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 dd9a80ca73d2606b22414a5a0d675100eeecb51e..754f2b3cfbe4cc09d389b9505465286d14781937 100644 |
--- a/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart |
+++ b/sdk/lib/_internal/compiler/implementation/js_backend/backend.dart |
@@ -68,6 +68,13 @@ class JavaScriptBackend extends Backend { |
static const String INVOKE_ON = '_getCachedInvocation'; |
static const String START_ROOT_ISOLATE = 'startRootIsolate'; |
+ /// Set of classes that need to be considered for reflection although not |
+ /// otherwise visible during resolution. |
+ Iterable<ClassElement> get classesRequiredForReflection { |
+ // TODO(herhut): Clean this up when classes needed for rti are tracked. |
+ return [closureClass, jsIndexableClass]; |
+ } |
+ |
SsaBuilderTask builder; |
SsaOptimizerTask optimizer; |
SsaCodeGeneratorTask generator; |
@@ -287,9 +294,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>(); |
@@ -297,10 +301,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>(); |
@@ -392,7 +406,7 @@ class JavaScriptBackend extends Backend { |
} |
} |
- return isNeededForReflection(element.declaration); |
+ return isAccessibleByReflection(element.declaration); |
} |
bool canBeUsedForGlobalOptimizations(Element element) { |
@@ -797,7 +811,10 @@ class JavaScriptBackend extends Backend { |
registerCheckedModeHelpers(registry); |
} |
- onResolutionComplete() => rti.computeClassesNeedingRti(); |
+ onResolutionComplete() { |
+ computeMembersNeededForReflection(); |
+ rti.computeClassesNeedingRti(); |
+ } |
void registerGetRuntimeTypeArgument(Registry registry) { |
enqueueInResolution(getGetRuntimeTypeArgument(), registry); |
@@ -1445,11 +1462,17 @@ class JavaScriptBackend extends Backend { |
void registerNewSymbol(Registry registry) { |
} |
- /// Should [element] (a getter) be retained for reflection? |
- bool shouldRetainGetter(Element element) => isNeededForReflection(element); |
+ /// Should [element] (a getter) that would normally not be generated due to |
+ /// treeshaking be retained for reflection? |
+ bool shouldRetainGetter(Element element) { |
+ return isTreeShakingDisabled && isAccessibleByReflection(element); |
+ } |
- /// Should [element] (a setter) be retained for reflection? |
- bool shouldRetainSetter(Element element) => isNeededForReflection(element); |
+ /// Should [element] (a setter) hat would normally not be generated due to |
+ /// treeshaking be retained for reflection? |
+ bool shouldRetainSetter(Element element) { |
+ return isTreeShakingDisabled && isAccessibleByReflection(element); |
+ } |
/// Should [name] be retained for reflection? |
bool shouldRetainName(String name) { |
@@ -1462,7 +1485,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); |
@@ -1689,54 +1712,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; |
+ } 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 if the element matches a mirrorsUsed annotation. If |
+ * 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; |
@@ -1752,6 +1772,141 @@ 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 the correct ones when including the class. |
+ Map<ClassElement, List<Element>> closureMap = |
+ new Map<ClassElement, List<Element>>(); |
+ for (FunctionElement closure in compiler.resolverWorld.allClosures) { |
+ closureMap.putIfAbsent(closure.enclosingClass, () => []).add(closure); |
+ } |
+ bool foundClosure = false; |
+ 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 || cls.isInjected) continue; |
+ if (referencedFromMirrorSystem(cls)) { |
+ Set<Name> memberNames = new LinkedHashSet<Name>( |
+ equals: (Name a, Name b) => a.isSimilarTo(b), |
+ hashCode: (Name a) => a.similarHashCode); |
+ // 1) the class (should be live) |
+ assert(invariant(cls, resolution.isLive(cls))); |
+ reflectableMembers.add(cls); |
+ // 2) its constructors (if live) |
+ cls.constructors.forEach((Element constructor) { |
+ if (resolution.isLive(constructor)) { |
+ reflectableMembers.add(constructor); |
+ } |
+ }); |
+ // 3) all members, including fields via getter/setters (if live) |
+ 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 live) |
+ 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); |
+ foundClosure = true; |
+ } |
+ } else { |
+ // check members themselves |
+ cls.constructors.forEach((ConstructorElement element) { |
+ if (!compiler.enqueuer.resolution.isLive(element)) return; |
+ 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. Those might be reflectable is their enclosing |
+ // member is. |
+ List<Element> closures = closureMap[cls]; |
+ if (closures != null) { |
+ for (Element closure in closures) { |
+ if (referencedFromMirrorSystem(closure.enclosingMember, false)) { |
+ reflectableMembers.add(closure); |
+ foundClosure = true; |
+ } |
+ } |
+ } |
+ } |
+ } |
+ // 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.libraryLoader.libraries) { |
+ if (lib.isInternalLibrary) continue; |
+ lib.forEachLocalMember((Element member) { |
+ if (!member.isClass && |
+ compiler.enqueuer.resolution.isLive(member) && |
+ referencedFromMirrorSystem(member)) { |
+ reflectableMembers.add(member); |
+ } |
+ }); |
+ } |
+ // And closures inside top-level elements that do not have a surrounding |
+ // class. These will be in the [:null:] bucket of the [closureMap]. |
+ if (closureMap.containsKey(null)) { |
+ for (Element closure in closureMap[null]) { |
+ if (referencedFromMirrorSystem(closure)) { |
+ reflectableMembers.add(closure); |
+ foundClosure = true; |
+ } |
+ } |
+ } |
+ // As we do not think about closures as classes, yet, we have to make sure |
+ // their superclasses are available for reflection manually. |
+ if (foundClosure) { |
+ reflectableMembers.add(closureClass); |
+ } |
+ // It would be nice to have a better means to select |
+ Set<Element> closurizedMembers = compiler.resolverWorld.closurizedMembers; |
+ if (closurizedMembers.any(reflectableMembers.contains)) { |
+ reflectableMembers.add(boundClosureClass); |
+ } |
+ // 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'; |
@@ -1814,18 +1969,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 |
@@ -1845,6 +2000,7 @@ class JavaScriptBackend extends Backend { |
} |
metadataConstants.clear(); |
} |
+ return true; |
} |
void onElementResolved(Element element, TreeElements elements) { |