blob: ffe599739cbb6493d9317edb45e40ae1995d9cdc [file] [log] [blame]
// 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 '../common_elements.dart';
import '../compiler.dart';
import '../constants/values.dart';
import '../elements/elements.dart';
import '../elements/resolution_types.dart';
import '../options.dart';
import '../world.dart';
import '../universe/world_builder.dart';
import '../util/emptyset.dart';
import 'backend_helpers.dart';
import 'constant_handler_javascript.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;
/// True if a call to preserveUris has been seen and the preserve-uris flag
/// is set.
bool mustPreserveUris = 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>();
// TODO(johnniwinther): Avoid the need for this.
final Compiler _compiler;
final CompilerOptions _options;
final CommonElements _commonElements;
final BackendHelpers _helpers;
final JavaScriptConstantCompiler _constants;
MirrorsData(this._compiler, this._options, this._commonElements,
this._helpers, this._constants);
void registerUsedMember(MemberElement member) {
if (member == _helpers.disableTreeShakingMarker) {
isTreeShakingDisabled = true;
} else if (member == _helpers.preserveNamesMarker) {
mustPreserveNames = true;
} else if (member == _helpers.preserveMetadataMarker) {
mustRetainMetadata = true;
} else if (member == _helpers.preserveUrisMarker) {
if (_options.preserveUris) mustPreserveUris = true;
} else if (member == _helpers.preserveLibraryNamesMarker) {
mustRetainLibraryNames = true;
}
}
/// 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 =
_constants.getConstantValueForMetadata(metadata);
_constants.addCompileTimeConstantForEmission(constant);
}
return true;
}
return false;
}
bool invokedReflectively(Element element) {
if (element.isParameter) {
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 = _getDartClass(element);
}
return membersNeededForReflection.contains(element);
}
ClassElement _getDartClass(ClassElement cls) {
if (cls == _helpers.jsIntClass) {
return _commonElements.intClass;
} else if (cls == _helpers.jsBoolClass) {
return _commonElements.boolClass;
} else if (cls == _helpers.jsNumberClass) {
return _commonElements.numClass;
} else if (cls == _helpers.jsDoubleClass) {
return _commonElements.doubleClass;
} else if (cls == _helpers.jsStringClass) {
return _commonElements.stringClass;
} else if (cls == _helpers.jsArrayClass) {
return _commonElements.listClass;
} else if (cls == _helpers.jsNullClass) {
return _commonElements.nullClass;
} else {
return cls;
}
}
/// 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(_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(
ResolutionWorldBuilder worldBuilder, 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 worldBuilder.localFunctions) {
closureMap.putIfAbsent(closure.enclosingClass, () => []).add(closure);
}
bool foundClosure = false;
Set<Element> reflectableMembers = new Set<Element>();
for (ClassElement cls in 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((ConstructorElement constructor) {
if (worldBuilder.isMemberUsed(constructor)) {
reflectableMembers.add(constructor);
}
});
// 3) all members, including fields via getter/setters (if resolved)
cls.forEachClassMember((Member member) {
MemberElement element = member.element;
if (worldBuilder.isMemberUsed(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,
// worldBuilder.isMemberUsed(member.element)));
if (worldBuilder.isMemberUsed(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 (!worldBuilder.isMemberUsed(element)) return;
if (referencedFromMirrorSystem(element, false)) {
reflectableMembers.add(element);
}
});
cls.forEachClassMember((Member member) {
if (!worldBuilder.isMemberUsed(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 element) {
if (element.isClass || element.isTypedef) return;
MemberElement member = element;
if (worldBuilder.isMemberUsed(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 = _helpers.closureClass;
reflectableMembers.add(cls);
}
Set<MethodElement> closurizedMembers = worldBuilder.closurizedMembers;
if (closurizedMembers.any(reflectableMembers.contains)) {
ClassElement cls = _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));
}
}
}