| Index: pkg/compiler/lib/src/js_backend/mirrors_data.dart
|
| diff --git a/pkg/compiler/lib/src/js_backend/mirrors_data.dart b/pkg/compiler/lib/src/js_backend/mirrors_data.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6734fed8687d152767e1b05b3b1be10015dc7bb5
|
| --- /dev/null
|
| +++ b/pkg/compiler/lib/src/js_backend/mirrors_data.dart
|
| @@ -0,0 +1,370 @@
|
| +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +import '../closure.dart';
|
| +import '../common.dart';
|
| +import '../compiler.dart';
|
| +import '../constants/values.dart';
|
| +import '../elements/elements.dart';
|
| +import '../elements/resolution_types.dart';
|
| +import '../enqueue.dart';
|
| +import '../world.dart';
|
| +import '../util/emptyset.dart';
|
| +
|
| +class MirrorsData {
|
| + /// True if a call to preserveMetadataMarker has been seen. This means that
|
| + /// metadata must be retained for dart:mirrors to work correctly.
|
| + bool mustRetainMetadata = false;
|
| +
|
| + /// True if any metadata has been retained. This is slightly different from
|
| + /// [mustRetainMetadata] and tells us if any metadata was retained. For
|
| + /// example, if [mustRetainMetadata] is true but there is no metadata in the
|
| + /// program, this variable will stil be false.
|
| + bool hasRetainedMetadata = false;
|
| +
|
| + /// True if a call to preserveLibraryNames has been seen.
|
| + bool mustRetainLibraryNames = false;
|
| +
|
| + /// True if a call to preserveNames has been seen.
|
| + bool mustPreserveNames = false;
|
| +
|
| + /// True if a call to disableTreeShaking has been seen.
|
| + bool isTreeShakingDisabled = false;
|
| +
|
| + /// True if there isn't sufficient @MirrorsUsed data.
|
| + bool hasInsufficientMirrorsUsed = false;
|
| +
|
| + /// 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>();
|
| +
|
| + /// List of annotations provided by user that indicate that the annotated
|
| + /// element must be retained.
|
| + final Set<Element> metaTargetsUsed = new Set<Element>();
|
| +
|
| + final Compiler compiler;
|
| +
|
| + MirrorsData(this.compiler);
|
| +
|
| + /// 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) 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) {
|
| + if (hasInsufficientMirrorsUsed) return mustPreserveNames;
|
| + if (name == '') return false;
|
| + return symbolsUsed.contains(name);
|
| + }
|
| +
|
| + bool retainMetadataOf(Element element) {
|
| + if (mustRetainMetadata) hasRetainedMetadata = true;
|
| + if (mustRetainMetadata && referencedFromMirrorSystem(element)) {
|
| + for (MetadataAnnotation metadata in element.metadata) {
|
| + metadata.ensureResolved(compiler.resolution);
|
| + ConstantValue constant =
|
| + compiler.backend.constants.getConstantValueForMetadata(metadata);
|
| + compiler.backend.constants.addCompileTimeConstantForEmission(constant);
|
| + }
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool invokedReflectively(Element element) {
|
| + if (element.isRegularParameter || element.isInitializingFormal) {
|
| + ParameterElement parameter = element;
|
| + if (invokedReflectively(parameter.functionDeclaration)) return true;
|
| + }
|
| +
|
| + if (element.isField) {
|
| + if (Elements.isStaticOrTopLevel(element) &&
|
| + (element.isFinal || element.isConst)) {
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + return isAccessibleByReflection(element.declaration);
|
| + }
|
| +
|
| + /// Set of methods that are needed by reflection. Computed using
|
| + /// [computeMembersNeededForReflection] on first use.
|
| + Set<Element> _membersNeededForReflection = null;
|
| + Iterable<Element> get membersNeededForReflection {
|
| + assert(_membersNeededForReflection != null);
|
| + return _membersNeededForReflection;
|
| + }
|
| +
|
| + /// Called by [MirrorUsageAnalyzerTask] after it has merged all @MirrorsUsed
|
| + /// annotations. The arguments corresponds to the unions of the corresponding
|
| + /// fields of the annotations.
|
| + void registerMirrorUsage(
|
| + Set<String> symbols, Set<Element> targets, Set<Element> metaTargets) {
|
| + if (symbols == null && targets == null && metaTargets == null) {
|
| + // The user didn't specify anything, or there are imports of
|
| + // 'dart:mirrors' without @MirrorsUsed.
|
| + hasInsufficientMirrorsUsed = true;
|
| + return;
|
| + }
|
| + if (symbols != null) symbolsUsed.addAll(symbols);
|
| + if (targets != null) {
|
| + for (Element target in targets) {
|
| + if (target.isAbstractField) {
|
| + AbstractFieldElement field = target;
|
| + targetsUsed.add(field.getter);
|
| + targetsUsed.add(field.setter);
|
| + } else {
|
| + targetsUsed.add(target);
|
| + }
|
| + }
|
| + }
|
| + if (metaTargets != null) metaTargetsUsed.addAll(metaTargets);
|
| + }
|
| +
|
| + /**
|
| + * Returns `true` if [element] can be accessed through reflection, that is,
|
| + * is in the set of elements covered by a `MirrorsUsed` annotation.
|
| + *
|
| + * This property is used to tag emitted elements with a marker which is
|
| + * checked by the runtime system to throw an exception if an element is
|
| + * accessed (invoked, get, set) that is not accessible for the reflective
|
| + * system.
|
| + */
|
| + bool isAccessibleByReflection(Element element) {
|
| + if (element.isClass) {
|
| + element = compiler.backend.getDartClass(element);
|
| + }
|
| + return membersNeededForReflection.contains(element);
|
| + }
|
| +
|
| + /// Returns `true` if this member element needs reflection information at
|
| + /// runtime.
|
| + bool isMemberAccessibleByReflection(MemberElement element) {
|
| + return membersNeededForReflection.contains(element);
|
| + }
|
| +
|
| + /// Returns true if this element has to be enqueued due to
|
| + /// mirror usage. Might be a subset of [referencedFromMirrorSystem] if
|
| + /// normal tree shaking is still active ([isTreeShakingDisabled] is false).
|
| + bool requiredByMirrorSystem(Element element) {
|
| + return hasInsufficientMirrorsUsed && isTreeShakingDisabled ||
|
| + matchesMirrorsMetaTarget(element) ||
|
| + targetsUsed.contains(element);
|
| + }
|
| +
|
| + /// Returns true if this element is covered by a mirrorsUsed annotation.
|
| + ///
|
| + /// Note that it might still be ok to tree shake the element away if no
|
| + /// reflection is used in the program (and thus [isTreeShakingDisabled] is
|
| + /// still false). Therefore _do not_ use this predicate to decide inclusion
|
| + /// in the tree, use [requiredByMirrorSystem] instead.
|
| + bool referencedFromMirrorSystem(Element element, [recursive = true]) {
|
| + Element enclosing = recursive ? element.enclosingElement : null;
|
| +
|
| + 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 matchesMirrorsMetaTarget(Element element) {
|
| + if (metaTargetsUsed.isEmpty) return false;
|
| + for (MetadataAnnotation metadata in element.metadata) {
|
| + // TODO(kasperl): It would be nice if we didn't have to resolve
|
| + // all metadata but only stuff that potentially would match one
|
| + // of the used meta targets.
|
| + metadata.ensureResolved(compiler.resolution);
|
| + ConstantValue value =
|
| + compiler.constants.getConstantValue(metadata.constant);
|
| + if (value == null) continue;
|
| + ResolutionDartType type = value.getType(compiler.commonElements);
|
| + if (metaTargetsUsed.contains(type.element)) return true;
|
| + }
|
| + 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.
|
| + */
|
| + void computeMembersNeededForReflection(ClosedWorld closedWorld) {
|
| + if (_membersNeededForReflection != null) return;
|
| + if (closedWorld.commonElements.mirrorsLibrary == null) {
|
| + _membersNeededForReflection = const ImmutableEmptySet<Element>();
|
| + return;
|
| + }
|
| + // Compute a mapping from class to the closures it contains, so we
|
| + // can include the correct ones when including the class.
|
| + Map<ClassElement, List<LocalFunctionElement>> closureMap =
|
| + new Map<ClassElement, List<LocalFunctionElement>>();
|
| + for (LocalFunctionElement closure
|
| + in compiler.resolutionWorldBuilder.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.worldBuilder.directlyInstantiatedClasses) {
|
| + // Do not process internal classes.
|
| + if (cls.library.isInternalLibrary || cls.isInjected) continue;
|
| + if (referencedFromMirrorSystem(cls)) {
|
| + Set<Name> memberNames = new Set<Name>();
|
| + // 1) the class (should be resolved)
|
| + assert(invariant(cls, cls.isResolved));
|
| + reflectableMembers.add(cls);
|
| + // 2) its constructors (if resolved)
|
| + cls.constructors.forEach((Element constructor) {
|
| + if (resolution.hasBeenProcessed(constructor)) {
|
| + reflectableMembers.add(constructor);
|
| + }
|
| + });
|
| + // 3) all members, including fields via getter/setters (if resolved)
|
| + cls.forEachClassMember((Member member) {
|
| + MemberElement element = member.element;
|
| + if (resolution.hasBeenProcessed(element)) {
|
| + memberNames.add(member.name);
|
| + reflectableMembers.add(element);
|
| + element.nestedClosures
|
| + .forEach((SynthesizedCallMethodElementX callFunction) {
|
| + reflectableMembers.add(callFunction);
|
| + reflectableMembers.add(callFunction.closureClass);
|
| + });
|
| + }
|
| + });
|
| + // 4) all overriding members of subclasses/subtypes (should be resolved)
|
| + if (closedWorld.hasAnyStrictSubtype(cls)) {
|
| + closedWorld.forEachStrictSubtypeOf(cls, (ClassElement subcls) {
|
| + subcls.forEachClassMember((Member member) {
|
| + if (memberNames.contains(member.name)) {
|
| + // TODO(20993): find out why this assertion fails.
|
| + // assert(invariant(member.element,
|
| + // resolution.hasBeenProcessed(member.element)));
|
| + if (resolution.hasBeenProcessed(member.element)) {
|
| + reflectableMembers.add(member.element);
|
| + }
|
| + }
|
| + });
|
| + });
|
| + }
|
| + // 5) all its closures
|
| + List<LocalFunctionElement> closures = closureMap[cls];
|
| + if (closures != null) {
|
| + reflectableMembers.addAll(closures);
|
| + foundClosure = true;
|
| + }
|
| + } else {
|
| + // check members themselves
|
| + cls.constructors.forEach((ConstructorElement element) {
|
| + if (!resolution.hasBeenProcessed(element)) return;
|
| + if (referencedFromMirrorSystem(element, false)) {
|
| + reflectableMembers.add(element);
|
| + }
|
| + });
|
| + cls.forEachClassMember((Member member) {
|
| + if (!resolution.hasBeenProcessed(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<LocalFunctionElement> closures = closureMap[cls];
|
| + if (closures != null) {
|
| + for (LocalFunctionElement closure in closures) {
|
| + MemberElement member = closure.memberContext;
|
| + if (referencedFromMirrorSystem(member, 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 || member.isTypedef) &&
|
| + resolution.hasBeenProcessed(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) {
|
| + ClassElement cls = compiler.backend.helpers.closureClass;
|
| + reflectableMembers.add(cls);
|
| + }
|
| + Set<Element> closurizedMembers =
|
| + compiler.resolutionWorldBuilder.closurizedMembers;
|
| + if (closurizedMembers.any(reflectableMembers.contains)) {
|
| + ClassElement cls = compiler.backend.helpers.boundClosureClass;
|
| + reflectableMembers.add(cls);
|
| + }
|
| + // Add typedefs.
|
| + reflectableMembers
|
| + .addAll(closedWorld.allTypedefs.where(referencedFromMirrorSystem));
|
| + // Register all symbols of reflectable elements
|
| + for (Element element in reflectableMembers) {
|
| + symbolsUsed.add(element.name);
|
| + }
|
| + _membersNeededForReflection = reflectableMembers;
|
| + }
|
| +
|
| + // TODO(20791): compute closure classes after resolution and move this code to
|
| + // [computeMembersNeededForReflection].
|
| + void maybeMarkClosureAsNeededForReflection(
|
| + ClosureClassElement globalizedElement,
|
| + FunctionElement callFunction,
|
| + FunctionElement function) {
|
| + if (!_membersNeededForReflection.contains(function)) return;
|
| + _membersNeededForReflection.add(callFunction);
|
| + _membersNeededForReflection.add(globalizedElement);
|
| + }
|
| +
|
| + /// Called when [:const Symbol(name):] is seen.
|
| + void registerConstSymbol(String name) {
|
| + symbolsUsed.add(name);
|
| + if (name.endsWith('=')) {
|
| + symbolsUsed.add(name.substring(0, name.length - 1));
|
| + }
|
| + }
|
| +}
|
|
|