Chromium Code Reviews| Index: sdk/lib/_internal/compiler/implementation/js_backend/backend.dart |
| diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart |
| index 6722f4657cc36e859ad1c47cc6bd0f89d8a32da1..4c38f9a2a4bd2d9ceed19629674432fa28cfd89b 100644 |
| --- a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart |
| +++ b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart |
| @@ -265,9 +265,6 @@ class JavaScriptBackend extends Backend { |
| /// these constants must be registered. |
| final List<Dependency> metadataConstants = <Dependency>[]; |
| - /// List of symbols that the user has requested for reflection. |
| - final Set<String> symbolsUsed = new Set<String>(); |
| - |
| /// List of elements that the user has requested for reflection. |
| final Set<Element> targetsUsed = new Set<Element>(); |
| @@ -275,10 +272,20 @@ class JavaScriptBackend extends Backend { |
| /// element must be retained. |
| final Set<Element> metaTargetsUsed = new Set<Element>(); |
| + /// Set of methods that are needed by reflection. Computed using |
| + /// [computeMembersNeededForReflection] on first use. |
| + Iterable<Element> _membersNeededForReflection = null; |
| + Iterable<Element> get membersNeededForReflection { |
| + assert(_membersNeededForReflection != null); |
| + return _membersNeededForReflection; |
| + } |
| + |
| + /// List of symbols that the user has requested for reflection. |
| + final Set<String> symbolsUsed = new Set<String>(); |
| + |
| /// List of elements that the backend may use. |
| final Set<Element> helpersUsed = new Set<Element>(); |
| - |
| /// Set of typedefs that are used as type literals. |
| final Set<TypedefElement> typedefTypeLiterals = new Set<TypedefElement>(); |
| @@ -353,7 +360,7 @@ class JavaScriptBackend extends Backend { |
| } |
| } |
| - return isNeededForReflection(element.declaration); |
| + return isAccessibleByReflection(element.declaration); |
| } |
| bool canBeUsedForGlobalOptimizations(Element element) { |
| @@ -774,7 +781,10 @@ class JavaScriptBackend extends Backend { |
| registerCheckedModeHelpers(registry); |
| } |
| - onResolutionComplete() => rti.computeClassesNeedingRti(); |
| + onResolutionComplete() { |
| + computeMembersNeededForReflection(); |
| + rti.computeClassesNeedingRti(); |
| + } |
| void onStringInterpolation(Registry registry) { |
| assert(registry.isForResolution); |
| @@ -1574,10 +1584,14 @@ class JavaScriptBackend extends Backend { |
| } |
| /// Should [element] (a getter) be retained for reflection? |
| - bool shouldRetainGetter(Element element) => isNeededForReflection(element); |
| + bool shouldRetainGetter(Element element) { |
| + return isAccessibleByReflection(element); |
| + } |
| /// Should [element] (a setter) be retained for reflection? |
| - bool shouldRetainSetter(Element element) => isNeededForReflection(element); |
| + bool shouldRetainSetter(Element element) { |
| + return isAccessibleByReflection(element); |
| + } |
| /// Should [name] be retained for reflection? |
| bool shouldRetainName(String name) { |
| @@ -1590,7 +1604,7 @@ class JavaScriptBackend extends Backend { |
| bool retainMetadataOf(Element element) { |
| if (mustRetainMetadata) hasRetainedMetadata = true; |
| - if (mustRetainMetadata && isNeededForReflection(element)) { |
| + if (mustRetainMetadata && referencedFromMirrorSystem(element)) { |
| for (MetadataAnnotation metadata in element.metadata) { |
| metadata.ensureResolved(compiler); |
| Constant constant = constants.getConstantForMetadata(metadata); |
| @@ -1775,54 +1789,51 @@ class JavaScriptBackend extends Backend { |
| * system. |
| */ |
| bool isAccessibleByReflection(Element element) { |
| - // TODO(ahe): This isn't sufficient: simply importing dart:mirrors |
| - // causes hasInsufficientMirrorsUsed to become true. |
| - if (hasInsufficientMirrorsUsed) return true; |
| - return isNeededForReflection(element); |
| + if (element.isClass) { |
| + element = getDartClass(element); |
| + } |
| + // We have to treat closure classes specially here, as they only come into |
| + // existence after [membersNeededForReflection] has been computed. |
| + if (element is SynthesizedCallMethodElementX) { |
| + SynthesizedCallMethodElementX closure = element; |
| + element = closure.expression; |
|
Johnni Winther
2014/06/27 07:43:01
This could also be computed as closure.closureClas
herhut
2014/06/27 12:34:37
I think we have decided not to expose those fields
|
| + } else if (element is ClosureClassElement) { |
| + ClosureClassElement closure = element; |
| + element = closure.methodElement; |
| + } |
| + return membersNeededForReflection.contains(element); |
| } |
| /** |
| - * Returns `true` if the emitter must emit the element even though there |
| - * is no direct use in the program, but because the reflective system may |
| - * need to access it. |
| + * Returns true if the element has to be resolved due to a mirrorsUsed |
| + * annotation. If we have insufficient mirrors used annotations, we only |
| + * keep additonal elements if treeshaking has been disabled. |
| */ |
| - bool isNeededForReflection(Element element) { |
| - if (!isTreeShakingDisabled) return false; |
| - element = getDartClass(element); |
| - if (hasInsufficientMirrorsUsed) return isTreeShakingDisabled; |
| - /// Record the name of [element] in [symbolsUsed]. Return true for |
| - /// convenience. |
| - bool registerNameOf(Element element) { |
| - symbolsUsed.add(element.name); |
| - if (element.isConstructor) { |
| - symbolsUsed.add(element.enclosingClass.name); |
| - } |
| - return true; |
| - } |
| - |
| - Element enclosing = element.enclosingElement; |
| - if (enclosing != null && isNeededForReflection(enclosing)) { |
| - return registerNameOf(element); |
| - } |
| - |
| - if (isNeededThroughMetaTarget(element)) { |
| - return registerNameOf(element); |
| - } |
| + bool requiredByMirrorSystem(Element element) { |
| + return hasInsufficientMirrorsUsed && isTreeShakingDisabled || |
| + matchesMirrorsMetaTarget(element) || |
| + targetsUsed.contains(element); |
| + } |
| - if (!targetsUsed.isEmpty && targetsUsed.contains(element)) { |
| - return registerNameOf(element); |
| - } |
| + /** |
| + * Returns true of the element matches a mirrorsUsed annotation. If |
|
Johnni Winther
2014/06/27 07:43:01
'of' -> 'if'.
herhut
2014/06/27 12:34:37
Done.
|
| + * we have insufficient mirrorsUsed information, this returns true for |
| + * all elements, as they might all be potentially referenced. |
| + */ |
| + bool referencedFromMirrorSystem(Element element, [recursive = true]) { |
| + Element enclosing = recursive ? element.enclosingElement : null; |
| - // TODO(kasperl): Consider caching this information. It is consulted |
| - // multiple times because of the way we deal with the enclosing element. |
| - return false; |
| + return hasInsufficientMirrorsUsed || |
| + matchesMirrorsMetaTarget(element) || |
| + targetsUsed.contains(element) || |
| + (enclosing != null && referencedFromMirrorSystem(enclosing)); |
| } |
| /** |
| * Returns `true` if the element is needed because it has an annotation |
| * of a type that is used as a meta target for reflection. |
| */ |
| - bool isNeededThroughMetaTarget(Element element) { |
| + bool matchesMirrorsMetaTarget(Element element) { |
| if (metaTargetsUsed.isEmpty) return false; |
| for (Link link = element.metadata; !link.isEmpty; link = link.tail) { |
| MetadataAnnotation metadata = link.head; |
| @@ -1838,6 +1849,128 @@ class JavaScriptBackend extends Backend { |
| return false; |
| } |
| + /** |
| + * Visits all classes and computes whether its members are needed for |
| + * reflection. |
| + * |
| + * We have to precompute this set as we cannot easily answer the need for |
| + * reflection locally when looking at the member: We lack the information by |
| + * which classes a member is inherited. Called after resolution is complete. |
| + * |
| + * We filter out private libraries here, as their elements should not |
| + * be visible by reflection unless some other interfaces makes them |
| + * accessible. |
| + */ |
| + computeMembersNeededForReflection() { |
| + if (_membersNeededForReflection != null) return; |
| + if (compiler.mirrorsLibrary == null) { |
| + _membersNeededForReflection = const []; |
| + } |
| + // Compute a mapping from class to the closures it contains, so we |
| + // can include include the correct ones when including the class. |
|
Johnni Winther
2014/06/27 07:43:01
'include include' -> 'include'.
herhut
2014/06/27 12:34:37
Done.
|
| + Map<ClassElement, List<Element>> closureMap = |
| + new Map<ClassElement, List<Element>>(); |
| + for (Element clos in compiler.resolverWorld.allClosures) { |
|
Johnni Winther
2014/06/27 07:43:01
'Element clos' -> 'FunctionElement closure'.
herhut
2014/06/27 12:34:37
Done.
|
| + closureMap.putIfAbsent(clos.enclosingClass, () => []).add(clos); |
| + } |
| + Set<Element> reflectableMembers = new Set<Element>(); |
| + ResolutionEnqueuer resolution = compiler.enqueuer.resolution; |
| + for (ClassElement cls in resolution.universe.instantiatedClasses) { |
| + // Do not process internal classes. |
| + if (cls.library.isInternalLibrary) continue; |
| + if (cls.isInjected) continue; |
|
Johnni Winther
2014/06/27 07:43:01
Merge conditions.
herhut
2014/06/27 12:34:37
Done.
|
| + if (referencedFromMirrorSystem(cls)) { |
| + Set<Name> memberNames = new LinkedHashSet<Name>( |
|
karlklose
2014/06/27 07:45:57
Consider moving this logic to its own method.
herhut
2014/06/27 12:34:37
It has side effects on reflectableMembers and I'd
|
| + equals: (Name a, Name b) => a.isSimilarTo(b), |
| + hashCode: (Name a) => a.similarHashCode); |
| + // 1) the class (should be life) |
|
Johnni Winther
2014/06/27 07:43:01
'life' -> 'live' here and more below.
karlklose
2014/06/27 07:45:57
'life' -> 'live'. (Also below.)
herhut
2014/06/27 12:34:37
Done.
herhut
2014/06/27 12:34:37
Done.
|
| + assert(invariant(cls, resolution.isLive(cls))); |
| + reflectableMembers.add(cls); |
| + // 2) its constructors (if life) |
| + cls.constructors.forEach((Element constructor) { |
|
karlklose
2014/06/27 07:45:57
How about:
cls.constructors
.where(resolut
herhut
2014/06/27 12:34:37
constructors is a Link<Element> and has no filter
|
| + if (resolution.isLive(constructor)) { |
| + reflectableMembers.add(constructor); |
| + } |
| + }); |
| + // 3) all members, including fields via getter/setters (if life) |
| + cls.forEachClassMember((Member member) { |
| + if (resolution.isLive(member.element)) { |
| + memberNames.add(member.name); |
| + reflectableMembers.add(member.element); |
| + } |
| + }); |
| + // 4) all overriding members of subclasses/subtypes (should be life) |
| + if (compiler.world.hasAnySubtype(cls)) { |
| + for (ClassElement subcls in compiler.world.subtypesOf(cls)) { |
| + subcls.forEachClassMember((Member member) { |
| + if (memberNames.contains(member.name)) { |
| + assert(invariant(member.element, |
| + resolution.isLive(member.element))); |
| + reflectableMembers.add(member.element); |
| + } |
| + }); |
| + } |
| + } |
| + // 5) all its closures |
| + List<Element> closures = closureMap[cls]; |
| + if (closures != null) { |
| + reflectableMembers.addAll(closures); |
| + } |
| + } else { |
| + // check members themselves |
| + cls.constructors.forEach((ConstructorElement element) { |
|
karlklose
2014/06/27 07:45:57
Could you share the code of this closure with the
herhut
2014/06/27 12:34:37
I would need an adapter anyway from member to memb
|
| + if (!compiler.enqueuer.resolution.isLive(element)) return; |
|
Johnni Winther
2014/06/27 07:43:01
[cls] should be live if the constructor is.
herhut
2014/06/27 12:34:37
Yes, but [cls] is not reflectable. This handles th
|
| + if (referencedFromMirrorSystem(element, false)) { |
| + reflectableMembers.add(element); |
| + } |
| + }); |
| + cls.forEachClassMember((Member member) { |
| + if (!compiler.enqueuer.resolution.isLive(member.element)) return; |
| + if (referencedFromMirrorSystem(member.element, false)) { |
| + reflectableMembers.add(member.element); |
| + } |
| + }); |
| + // Also add in closures |
| + List<Element> closures = closureMap[cls]; |
| + if (closures != null) { |
| + for (Element closure in closures) { |
| + if (referencedFromMirrorSystem(closure.enclosingMember, false)) { |
| + reflectableMembers.add(closure); |
| + } |
| + } |
| + } |
| + } |
| + } |
| + // We also need top-level non-class elements like static functions and |
| + // global fields. We use the resolution queue to decide which elements are |
| + // part of the live world. |
| + for (LibraryElement lib in compiler.libraries.values) { |
| + if (lib.isInternalLibrary) continue; |
| + lib.forEachLocalMember((Element member) { |
| + if (!member.isClass && |
| + compiler.enqueuer.resolution.isLive(member) && |
| + referencedFromMirrorSystem(member)) { |
| + reflectableMembers.add(member); |
| + } |
| + }); |
| + } |
| + // Lastly, we add in all the closures found if they match. |
| + for (Element clos in compiler.resolverWorld.allClosures) { |
|
Johnni Winther
2014/06/27 07:43:01
'Element clos' -> 'FunctionElement closure'.
herhut
2014/06/27 12:34:37
Done.
|
| + if (clos.enclosingMember == null || |
| + referencedFromMirrorSystem(clos.enclosingMember)) { |
| + reflectableMembers.add(clos); |
| + } |
| + } |
| + // And their superclasses. |
| + reflectableMembers.add(compiler.closureClass); |
|
Johnni Winther
2014/06/27 07:43:01
Only if 'allClosures' is non-empty?
herhut
2014/06/27 12:34:37
The [Closure] class could still be part of a Mirro
|
| + reflectableMembers.add(compiler.boundClosureClass); |
|
Johnni Winther
2014/06/27 07:43:01
Only if we have tear-offs?
herhut
2014/06/27 12:34:37
See above.
|
| + // Register all symbols of reflectable elements |
| + for (Element element in reflectableMembers) { |
| + symbolsUsed.add(element.name); |
| + } |
| + _membersNeededForReflection = reflectableMembers; |
| + } |
| + |
| jsAst.Call generateIsJsIndexableCall(jsAst.Expression use1, |
| jsAst.Expression use2) { |
| String dispatchPropertyName = 'init.dispatchPropertyName'; |
| @@ -1900,18 +2033,18 @@ class JavaScriptBackend extends Backend { |
| } |
| /// Called when [enqueuer] is empty, but before it is closed. |
| - void onQueueEmpty(Enqueuer enqueuer) { |
| + bool onQueueEmpty(Enqueuer enqueuer, Iterable<ClassElement> recentClasses) { |
| // Add elements referenced only via custom elements. Return early if any |
| // elements are added to avoid counting the elements as due to mirrors. |
| customElementsAnalysis.onQueueEmpty(enqueuer); |
| - if (!enqueuer.queueIsEmpty) return; |
| + if (!enqueuer.queueIsEmpty) return false; |
| if (!enqueuer.isResolutionQueue && preMirrorsMethodCount == 0) { |
| preMirrorsMethodCount = generatedCode.length; |
| } |
| if (isTreeShakingDisabled) { |
| - enqueuer.enqueueEverything(); |
| + enqueuer.enqueueReflectiveElements(recentClasses); |
| } else if (!targetsUsed.isEmpty && enqueuer.isResolutionQueue) { |
| // Add all static elements (not classes) that have been requested for |
| // reflection. If there is no mirror-usage these are probably not |
| @@ -1931,6 +2064,7 @@ class JavaScriptBackend extends Backend { |
| } |
| metadataConstants.clear(); |
| } |
| + return true; |
| } |
| void onElementResolved(Element element, TreeElements elements) { |