| // 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 '../common.dart'; |
| import '../common/elements.dart'; |
| import '../common/names.dart' show Identifiers, Names; |
| import '../constants/values.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/types.dart'; |
| import '../js_backend/annotations.dart'; |
| import '../js_backend/field_analysis.dart' show KFieldAnalysis; |
| import '../js_backend/backend_usage.dart' |
| show BackendUsage, BackendUsageBuilder; |
| import '../js_backend/interceptor_data.dart' show InterceptorDataBuilder; |
| import '../js_backend/native_data.dart' show NativeBasicData, NativeDataBuilder; |
| import '../js_backend/no_such_method_registry.dart'; |
| import '../js_backend/runtime_types_resolution.dart'; |
| import '../kernel/element_map.dart'; |
| import '../kernel/kernel_world.dart'; |
| import '../native/enqueue.dart' show NativeResolutionEnqueuer; |
| import '../options.dart'; |
| import '../util/enumset.dart'; |
| import '../util/util.dart'; |
| import '../world.dart' show World; |
| import 'call_structure.dart'; |
| import 'class_hierarchy.dart' show ClassHierarchyBuilder; |
| import 'class_set.dart'; |
| import 'member_usage.dart'; |
| import 'selector.dart' show Selector; |
| import 'use.dart' |
| show ConstantUse, DynamicUse, DynamicUseKind, StaticUse, StaticUseKind; |
| import 'world_builder.dart'; |
| |
| /// The type and kind of an instantiation registered through |
| /// `ResolutionWorldBuilder.registerTypeInstantiation`. |
| class Instance { |
| final InterfaceType type; |
| final Instantiation kind; |
| |
| Instance(this.type, this.kind); |
| |
| @override |
| int get hashCode { |
| return Hashing.objectHash(type, Hashing.objectHash(kind)); |
| } |
| |
| @override |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is! Instance) return false; |
| return type == other.type && kind == other.kind; |
| } |
| |
| @override |
| String toString() { |
| StringBuffer sb = StringBuffer(); |
| sb.write(type); |
| if (kind == Instantiation.directlyInstantiated) { |
| sb.write(' directly'); |
| } else if (kind == Instantiation.abstractlyInstantiated) { |
| sb.write(' abstractly'); |
| } else if (kind == Instantiation.uninstantiated) { |
| sb.write(' none'); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| /// Information about instantiations of a class. |
| class InstantiationInfo { |
| /// A map from constructor of the class to their instantiated types. |
| /// |
| /// For instance |
| /// |
| /// import 'dart:html'; |
| /// |
| /// abstract class AbstractClass<S> { |
| /// factory AbstractClass.a() = Class<S>.a; |
| /// factory AbstractClass.b() => Class<S>.b(); |
| /// } |
| /// class Class<T> implements AbstractClass<T> { |
| /// Class.a(); |
| /// Class.b(); |
| /// factory Class.c() = Class.b<T>; |
| /// } |
| /// |
| /// |
| /// main() { |
| /// Class.a(); |
| /// Class<int>.a(); |
| /// Class<String>.b(); |
| /// Class<num>.c(); |
| /// AbstractClass<double>.a(); |
| /// AbstractClass<bool>.b(); |
| /// DivElement(); // native instantiation |
| /// } |
| /// |
| /// will generate the mappings |
| /// |
| /// AbstractClass: { |
| /// AbstractClass.a: { |
| /// AbstractClass<double> none, // from `new AbstractClass<double>.a()` |
| /// }, |
| /// AbstractClass.b: { |
| /// AbstractClass<bool> none, // from `new AbstractClass<bool>.b()` |
| /// }, |
| /// }, |
| /// Class: { |
| /// Class.a: { |
| /// Class directly, // from `new Class.a()` |
| /// Class<int> directly, // from `new Class<int>.a()` |
| /// Class<S> directly redirect, // from `factory AbstractClass.a` |
| /// }, |
| /// Class.b: { |
| /// Class<String> directly, // from `new Class<String>.b()` |
| /// Class<T> directly redirect, // from `factory Class.c` |
| /// Class<S> directly, // from `factory AbstractClass.b` |
| /// }, |
| /// Class.c: { |
| /// Class<num> directly, // from `new Class<num>.c()` |
| /// }, |
| /// }, |
| /// DivElement: { |
| /// DivElement: { |
| /// DivElement abstractly, // from `new DivElement()` |
| /// }, |
| /// } |
| /// |
| /// If the constructor is unknown, for instance for native or mirror usage, |
| /// `null` is used as key. |
| Map<ConstructorEntity?, Set<Instance>>? instantiationMap; |
| |
| /// Register [type] as the instantiation [kind] using [constructor]. |
| void addInstantiation( |
| ConstructorEntity? constructor, |
| InterfaceType type, |
| Instantiation kind, |
| ) { |
| (instantiationMap ??= {}) |
| .putIfAbsent(constructor, () => <Instance>{}) |
| .add(Instance(type, kind)); |
| switch (kind) { |
| case Instantiation.directlyInstantiated: |
| isDirectlyInstantiated = true; |
| break; |
| case Instantiation.abstractlyInstantiated: |
| isAbstractlyInstantiated = true; |
| break; |
| case Instantiation.uninstantiated: |
| break; |
| case Instantiation.indirectlyInstantiated: |
| throw StateError("Instantiation $kind is not allowed."); |
| } |
| } |
| |
| /// `true` if the class is either directly or abstractly instantiated. |
| bool get hasInstantiation => |
| isDirectlyInstantiated || isAbstractlyInstantiated; |
| |
| /// `true` if the class is directly instantiated. |
| bool isDirectlyInstantiated = false; |
| |
| /// `true` if the class is abstractly instantiated. |
| bool isAbstractlyInstantiated = false; |
| |
| @override |
| String toString() { |
| StringBuffer sb = StringBuffer(); |
| sb.write('InstantiationInfo['); |
| if (instantiationMap != null) { |
| bool needsComma = false; |
| instantiationMap!.forEach(( |
| ConstructorEntity? constructor, |
| Set<Instance> set, |
| ) { |
| if (needsComma) { |
| sb.write(', '); |
| } |
| if (constructor != null) { |
| sb.write(constructor); |
| } else { |
| sb.write('<unknown>'); |
| } |
| sb.write(': '); |
| sb.write(set); |
| needsComma = true; |
| }); |
| } |
| sb.write(']'); |
| return sb.toString(); |
| } |
| } |
| |
| class ResolutionWorldBuilder extends WorldBuilder implements World { |
| /// Instantiation information for all classes with instantiated types. |
| /// |
| /// Invariant: Elements are declaration elements. |
| final Map<ClassEntity, InstantiationInfo> _instantiationInfo = {}; |
| |
| /// Classes implemented by directly instantiated classes. |
| final Set<ClassEntity> _implementedClasses = {}; |
| |
| /// The set of all referenced static fields. |
| /// |
| /// Invariant: Elements are declaration elements. |
| final Set<FieldEntity> _allReferencedStaticFields = {}; |
| |
| /// Documentation wanted -- johnniwinther |
| /// |
| /// Invariant: Elements are declaration elements. |
| final Set<FunctionEntity> _methodsNeedingSuperGetter = {}; |
| final Map<String, Map<Selector, SelectorConstraints>> _invokedNames = {}; |
| final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters = {}; |
| final Map<String, Map<Selector, SelectorConstraints>> _invokedSetters = {}; |
| |
| final Map<ClassEntity, ClassUsage> _processedClasses = {}; |
| |
| Map<ClassEntity, ClassUsage> get classUsageForTesting => _processedClasses; |
| |
| /// Map of registered usage of members of live classes. |
| final Map<MemberEntity, MemberUsage> _memberUsage = {}; |
| |
| Map<MemberEntity, MemberUsage> get memberUsageForTesting => _memberUsage; |
| |
| /// Map containing instance members of live classes that have not yet been |
| /// fully invoked dynamically. |
| /// |
| /// A method is fully invoked if all is optional parameter have been passed |
| /// in some invocation. |
| final Map<String, Set<MemberUsage>> _invokableInstanceMembersByName = {}; |
| |
| /// Map containing instance members of live classes that have not yet been |
| /// read from dynamically. |
| final Map<String, Set<MemberUsage>> _readableInstanceMembersByName = {}; |
| |
| /// Map containing instance members of live classes that have not yet been |
| /// written to dynamically. |
| final Map<String, Set<MemberUsage>> _writableInstanceMembersByName = {}; |
| |
| final Set<FieldEntity> _fieldSetters = {}; |
| |
| final Set<DartType> _isChecks = {}; |
| final Set<TypeVariableType> _namedTypeVariables = {}; |
| |
| final Set<RecordType> _instantiatedRecordTypes = {}; |
| |
| /// Set of all closures in the program. Used by the mirror tracking system |
| /// to find all live closure instances. |
| final Set<Local> _localFunctions = {}; |
| |
| final Set<FunctionEntity> _closurizedMembersWithFreeTypeVariables = {}; |
| |
| final CompilerOptions _options; |
| final ElementEnvironment elementEnvironment; |
| final DartTypes _dartTypes; |
| final CommonElements _commonElements; |
| |
| final NativeBasicData _nativeBasicData; |
| final NativeDataBuilder _nativeDataBuilder; |
| final InterceptorDataBuilder _interceptorDataBuilder; |
| final BackendUsageBuilder _backendUsageBuilder; |
| final RuntimeTypesNeedBuilder _rtiNeedBuilder; |
| final KFieldAnalysis _allocatorAnalysis; |
| final NativeResolutionEnqueuer _nativeResolutionEnqueuer; |
| final NoSuchMethodRegistry _noSuchMethodRegistry; |
| final AnnotationsDataBuilder _annotationsDataBuilder; |
| |
| final SelectorConstraintsStrategy _selectorConstraintsStrategy; |
| final ClassHierarchyBuilder classHierarchyBuilder; |
| |
| bool _closed = false; |
| KClosedWorld? _closedWorldCache; |
| final Set<MemberEntity> _liveInstanceMembers = {}; |
| final Set<MemberEntity> _liveAbstractInstanceMembers = {}; |
| |
| final Set<ConstantValue> _constantValues = {}; |
| |
| final Set<Local> _genericLocalFunctions = {}; |
| |
| final Set<MemberEntity> _pendingWeakTearOffs = {}; |
| |
| final Set<MemberEntity> _processedMembers = {}; |
| |
| bool get isClosed => _closed; |
| |
| final KernelToElementMap _elementMap; |
| |
| ResolutionWorldBuilder( |
| this._options, |
| this._elementMap, |
| this.elementEnvironment, |
| this._dartTypes, |
| this._commonElements, |
| this._nativeBasicData, |
| this._nativeDataBuilder, |
| this._interceptorDataBuilder, |
| this._backendUsageBuilder, |
| this._rtiNeedBuilder, |
| this._allocatorAnalysis, |
| this._nativeResolutionEnqueuer, |
| this._noSuchMethodRegistry, |
| this._annotationsDataBuilder, |
| this._selectorConstraintsStrategy, |
| this.classHierarchyBuilder, |
| ); |
| |
| /// Returns the classes registered as directly or indirectly instantiated. |
| Iterable<ClassEntity> get processedClasses => _processedClasses.keys.where( |
| (cls) => _processedClasses[cls]!.isInstantiated, |
| ); |
| |
| bool isMemberProcessed(MemberEntity member) => |
| _processedMembers.contains(member); |
| |
| void registerProcessedMember(MemberEntity member) { |
| _processedMembers.add(member); |
| } |
| |
| Iterable<MemberEntity> get processedMembers => _processedMembers; |
| |
| /// The closed world computed by this world builder. |
| /// |
| /// This is only available after the world builder has been closed. |
| KClosedWorld? get closedWorldForTesting { |
| if (!_closed) { |
| failedAt( |
| noLocationSpannable, |
| "The world builder has not yet been closed.", |
| ); |
| } |
| return _closedWorldCache; |
| } |
| |
| // TODO(johnniwinther): Improve semantic precision. |
| Iterable<ClassEntity> get directlyInstantiatedClasses { |
| Set<ClassEntity> classes = {}; |
| getInstantiationMap().forEach((ClassEntity cls, InstantiationInfo info) { |
| if (info.hasInstantiation) { |
| classes.add(cls); |
| } |
| }); |
| return classes; |
| } |
| |
| /// Registers that [element] has been closurized. |
| void registerClosurizedMember(MemberEntity element) { |
| FunctionType type = elementEnvironment.getFunctionType( |
| element as FunctionEntity, |
| ); |
| if (type.containsTypeVariables) { |
| _closurizedMembersWithFreeTypeVariables.add(element); |
| } |
| } |
| |
| /// Register [type] as (directly) instantiated. |
| // TODO(johnniwinther): Fully enforce the separation between exact, through |
| // subclass and through subtype instantiated types/classes. |
| // TODO(johnniwinther): Support unknown type arguments for generic types. |
| void registerTypeInstantiation( |
| InterfaceType type, |
| ClassUsedCallback classUsed, { |
| ConstructorEntity? constructor, |
| }) { |
| ClassEntity cls = type.element; |
| InstantiationInfo info = _instantiationInfo.putIfAbsent( |
| cls, |
| () => InstantiationInfo(), |
| ); |
| Instantiation kind = Instantiation.uninstantiated; |
| bool isNative = _nativeBasicData.isNativeClass(cls); |
| // We can't use the closed-world assumption with native abstract |
| // classes; a native abstract class may have non-abstract subclasses |
| // not declared to the program. Instances of these classes are |
| // indistinguishable from the abstract class. |
| if (!cls.isAbstract || isNative) { |
| if (isNative) { |
| kind = Instantiation.abstractlyInstantiated; |
| } else { |
| kind = Instantiation.directlyInstantiated; |
| } |
| } |
| info.addInstantiation(constructor, type, kind); |
| if (kind != Instantiation.uninstantiated) { |
| classHierarchyBuilder.updateClassHierarchyNodeForClass( |
| cls, |
| directlyInstantiated: info.isDirectlyInstantiated, |
| abstractlyInstantiated: info.isAbstractlyInstantiated, |
| ); |
| _processInstantiatedClass(cls, classUsed); |
| } |
| |
| // TODO(johnniwinther): Use [_instantiationInfo] to compute this information |
| // instead. |
| if (_implementedClasses.add(cls)) { |
| classUsed(cls, _getClassUsage(cls).implement()); |
| elementEnvironment.forEachSupertype(cls, (InterfaceType supertype) { |
| if (_implementedClasses.add(supertype.element)) { |
| classUsed( |
| supertype.element, |
| _getClassUsage(supertype.element).implement(), |
| ); |
| } |
| }); |
| } |
| } |
| |
| void registerRecordTypeInstantiation(RecordType type) { |
| _instantiatedRecordTypes.add(type); |
| } |
| |
| Iterable<CallStructure> _getMatchingCallStructures( |
| Map<Selector, SelectorConstraints>? selectors, |
| MemberEntity member, |
| ) { |
| if (selectors == null) return const []; |
| Set<CallStructure>? callStructures; |
| for (Selector selector in selectors.keys) { |
| if (selector.appliesUnnamed(member)) { |
| SelectorConstraints masks = selectors[selector]!; |
| if (masks.canHit(member, selector.memberName, this)) { |
| callStructures ??= {}; |
| callStructures.add(selector.callStructure); |
| } |
| } |
| } |
| return callStructures ?? const []; |
| } |
| |
| bool _hasMatchingSelector( |
| Map<Selector, SelectorConstraints>? selectors, |
| MemberEntity member, |
| ) { |
| if (selectors == null) return false; |
| for (Selector selector in selectors.keys) { |
| if (selector.appliesUnnamed(member)) { |
| SelectorConstraints masks = selectors[selector]!; |
| if (masks.canHit(member, selector.memberName, this)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /// Returns the instantiation map used for computing the closed world. |
| Map<ClassEntity, InstantiationInfo> getInstantiationMap() { |
| return _instantiationInfo; |
| } |
| |
| Iterable<CallStructure> _getInvocationCallStructures(MemberEntity member) { |
| return _getMatchingCallStructures(_invokedNames[member.name], member); |
| } |
| |
| bool _hasInvokedGetter(MemberEntity member) { |
| return _hasMatchingSelector(_invokedGetters[member.name], member); |
| } |
| |
| bool _hasInvokedSetter(MemberEntity member) { |
| return _hasMatchingSelector(_invokedSetters[member.name], member); |
| } |
| |
| /// Applies the [dynamicUse] to applicable instance members. Calls |
| /// [membersUsed] with the usage changes for each member. |
| void registerDynamicUse( |
| DynamicUse dynamicUse, |
| MemberUsedCallback memberUsed, |
| ) { |
| Selector selector = dynamicUse.selector; |
| String methodName = selector.name; |
| |
| void process( |
| Map<String, Set<MemberUsage>> memberMap, |
| EnumSet<MemberUse> Function(MemberUsage usage) action, |
| bool Function(MemberUsage usage) shouldBeRemoved, |
| ) { |
| _processSet(memberMap, methodName, (MemberUsage usage) { |
| if (usage.entity.isAbstract || |
| selector.appliesUnnamed(usage.entity) && |
| (_selectorConstraintsStrategy.appliedUnnamed( |
| dynamicUse, |
| usage.entity, |
| this, |
| ))) { |
| memberUsed(usage.entity, action(usage)); |
| return shouldBeRemoved(usage); |
| } |
| return false; |
| }); |
| } |
| |
| switch (dynamicUse.kind) { |
| case DynamicUseKind.invoke: |
| registerDynamicInvocation( |
| dynamicUse.selector, |
| dynamicUse.typeArguments, |
| ); |
| if (_registerNewSelector(dynamicUse, _invokedNames)) { |
| process( |
| _invokableInstanceMembersByName, |
| (m) => m.invoke( |
| Accesses.dynamicAccess, |
| dynamicUse.selector.callStructure, |
| ), |
| // If not all optional parameters have been passed in invocations |
| // we must keep the member in [_invokableInstanceMembersByName]. |
| // TODO(johnniwinther): Also remove from |
| // [_readableInstanceMembersByName] in case of getters/setters. |
| (u) => !u.hasPendingDynamicInvoke, |
| ); |
| } |
| break; |
| case DynamicUseKind.get: |
| if (_registerNewSelector(dynamicUse, _invokedGetters)) { |
| process( |
| _readableInstanceMembersByName, |
| (m) => m.read(Accesses.dynamicAccess), |
| // TODO(johnniwinther): Members cannot be partially read so |
| // we should always remove them. |
| // TODO(johnniwinther): Also remove from |
| // [_invokableInstanceMembersByName] in case of methods. |
| (u) => !u.hasPendingDynamicRead, |
| ); |
| } |
| break; |
| case DynamicUseKind.set: |
| if (_registerNewSelector(dynamicUse, _invokedSetters)) { |
| process( |
| _writableInstanceMembersByName, |
| (m) => m.write(Accesses.dynamicAccess), |
| // TODO(johnniwinther): Members cannot be partially written so |
| // we should always remove them. |
| (u) => !u.hasPendingDynamicWrite, |
| ); |
| } |
| break; |
| } |
| } |
| |
| bool _registerNewSelector( |
| DynamicUse dynamicUse, |
| Map<String, Map<Selector, SelectorConstraints>> selectorMap, |
| ) { |
| Selector selector = dynamicUse.selector; |
| String name = selector.name; |
| Object? constraint = dynamicUse.receiverConstraint; |
| Map<Selector, SelectorConstraints> selectors = selectorMap.putIfAbsent( |
| name, |
| () => Maplet<Selector, SelectorConstraints>(), |
| ); |
| UniverseSelectorConstraints? constraints = |
| selectors[selector] as UniverseSelectorConstraints?; |
| if (constraints == null) { |
| selectors[selector] = _selectorConstraintsStrategy |
| .createSelectorConstraints(selector, constraint); |
| return true; |
| } |
| return constraints.addReceiverConstraint(constraint); |
| } |
| |
| /// Registers that [type] is checked in this world builder. |
| void registerIsCheck(covariant DartType type) { |
| _isChecks.add(type); |
| } |
| |
| void registerNamedTypeVariable(TypeVariableType type) { |
| _namedTypeVariables.add(type); |
| } |
| |
| /// Register the constant [use] with this world builder. Returns `true` if |
| /// the constant use was new to the world. |
| bool registerConstantUse(ConstantUse use) { |
| return _constantValues.add(use.value); |
| } |
| |
| /// Applies the [staticUse] to applicable members. Calls [membersUsed] with |
| /// the usage changes for each member. |
| void registerStaticUse(StaticUse staticUse, MemberUsedCallback memberUsed) { |
| if (staticUse.kind == StaticUseKind.closure) { |
| Local localFunction = staticUse.element as Local; |
| FunctionType type = elementEnvironment.getLocalFunctionType( |
| localFunction, |
| ); |
| if (type.typeVariables.isNotEmpty) { |
| _genericLocalFunctions.add(localFunction); |
| } |
| _localFunctions.add(localFunction); |
| return; |
| } else if (staticUse.kind == StaticUseKind.closureCall) { |
| final typeArguments = staticUse.typeArguments; |
| if (typeArguments != null && typeArguments.isNotEmpty) { |
| registerDynamicInvocation( |
| Selector.call(Names.call, staticUse.callStructure!), |
| typeArguments, |
| ); |
| } |
| return; |
| } |
| |
| MemberEntity element = staticUse.element as MemberEntity; |
| var (usage, useSet) = _getMemberUsage(element); |
| |
| if ((element.isStatic || element.isTopLevel) && element is FieldEntity) { |
| _allReferencedStaticFields.add(staticUse.element as FieldEntity); |
| } |
| // TODO(johnniwinther): Avoid this. Currently [FIELD_GET] and |
| // [FIELD_SET] contains [BoxFieldElement]s which we cannot enqueue. |
| // Also [CLOSURE] contains [LocalFunctionElement] which we cannot |
| // enqueue. |
| |
| switch (staticUse.kind) { |
| case StaticUseKind.instanceFieldGet: |
| break; |
| case StaticUseKind.instanceFieldSet: |
| _fieldSetters.add(staticUse.element as FieldEntity); |
| break; |
| case StaticUseKind.closure: |
| case StaticUseKind.closureCall: |
| // Already handled above. |
| break; |
| case StaticUseKind.superTearOff: |
| useSet = useSet.union(usage.read(Accesses.superAccess)); |
| _methodsNeedingSuperGetter.add(staticUse.element as FunctionEntity); |
| break; |
| case StaticUseKind.superFieldSet: |
| _fieldSetters.add(staticUse.element as FieldEntity); |
| useSet = useSet.union(usage.write(Accesses.superAccess)); |
| break; |
| case StaticUseKind.superGet: |
| useSet = useSet.union(usage.read(Accesses.superAccess)); |
| break; |
| case StaticUseKind.staticGet: |
| useSet = useSet.union(usage.read(Accesses.staticAccess)); |
| break; |
| case StaticUseKind.staticTearOff: |
| useSet = useSet.union(usage.read(Accesses.staticAccess)); |
| break; |
| case StaticUseKind.weakStaticTearOff: |
| if (usage.hasUse) { |
| useSet = useSet.union(usage.read(Accesses.staticAccess)); |
| } else { |
| _pendingWeakTearOffs.add(element); |
| } |
| break; |
| case StaticUseKind.superSetterSet: |
| useSet = useSet.union(usage.write(Accesses.superAccess)); |
| break; |
| case StaticUseKind.staticSet: |
| useSet = useSet.union(usage.write(Accesses.staticAccess)); |
| break; |
| case StaticUseKind.fieldInit: |
| useSet = useSet.union(usage.init()); |
| break; |
| case StaticUseKind.fieldConstantInit: |
| useSet = useSet.union(usage.constantInit(staticUse.constant!)); |
| break; |
| case StaticUseKind.superInvoke: |
| registerStaticInvocation(staticUse); |
| useSet = useSet.union( |
| usage.invoke(Accesses.superAccess, staticUse.callStructure!), |
| ); |
| break; |
| case StaticUseKind.staticInvoke: |
| registerStaticInvocation(staticUse); |
| useSet = useSet.union( |
| usage.invoke(Accesses.staticAccess, staticUse.callStructure!), |
| ); |
| if (_pendingWeakTearOffs.remove(element)) { |
| useSet = useSet.union(usage.read(Accesses.staticAccess)); |
| } |
| break; |
| case StaticUseKind.constructorInvoke: |
| case StaticUseKind.constConstructorInvoke: |
| useSet = useSet.union( |
| usage.invoke(Accesses.staticAccess, staticUse.callStructure!), |
| ); |
| break; |
| case StaticUseKind.directInvoke: |
| failedAt(element, 'Direct static use is not supported for resolution.'); |
| case StaticUseKind.inlining: |
| case StaticUseKind.callMethod: |
| failedAt( |
| currentElementSpannable, |
| "Static use ${staticUse.kind} is not supported during resolution.", |
| ); |
| } |
| if (useSet.isNotEmpty) { |
| memberUsed(usage.entity, useSet); |
| } |
| } |
| |
| /// Called to create a [ClassUsage] for [cls]. |
| /// |
| /// Subclasses override this to ensure needed invariants on [cls]. |
| ClassUsage _createClassUsage(covariant ClassEntity cls) => ClassUsage(cls); |
| |
| /// Return the canonical [ClassUsage] for [cls]. |
| ClassUsage _getClassUsage(ClassEntity cls) { |
| return _processedClasses.putIfAbsent(cls, () { |
| return _createClassUsage(cls); |
| }); |
| } |
| |
| /// Register [cls] and all its superclasses as instantiated. |
| void _processInstantiatedClass(ClassEntity cls, ClassUsedCallback classUsed) { |
| // Registers [superclass] as instantiated. Returns `true` if it wasn't |
| // already instantiated and we therefore have to process its superclass as |
| // well. |
| bool processClass(ClassEntity superclass) { |
| ClassUsage usage = _getClassUsage(superclass); |
| if (!usage.isInstantiated) { |
| classUsed(usage.cls, usage.instantiate()); |
| return true; |
| } |
| return false; |
| } |
| |
| ClassEntity? current = cls; |
| while (current != null && processClass(current)) { |
| current = elementEnvironment.getSuperClass(current); |
| } |
| } |
| |
| /// Computes usage for all members declared by [cls]. Calls [membersUsed] with |
| /// the usage changes for each member. |
| /// |
| /// If [checkEnqueuerConsistency] is `true` we check that no new member |
| /// usage can be found. This check is performed without changing the already |
| /// collected member usage. |
| void processClassMembers( |
| ClassEntity cls, |
| MemberUsedCallback memberUsed, { |
| bool checkEnqueuerConsistency = false, |
| }) { |
| elementEnvironment.forEachClassMember(cls, ( |
| ClassEntity cls, |
| MemberEntity member, |
| ) { |
| _processMemberInUsedClass( |
| cls, |
| member, |
| memberUsed, |
| checkEnqueuerConsistency: checkEnqueuerConsistency, |
| ); |
| }); |
| } |
| |
| void processAbstractClassMembers( |
| ClassEntity cls, |
| MemberUsedCallback memberUsed, |
| ) { |
| elementEnvironment.forEachLocalClassMember(cls, (MemberEntity member) { |
| if (member.isAbstract) { |
| // Check for potential usages of abstract members (i.e. of their |
| // overrides) and save them if they are used. |
| _processMemberInUsedClass(cls, member, memberUsed); |
| } |
| }); |
| } |
| |
| /// Call [updateUsage] on all [MemberUsage]s in the set in [map] for |
| /// [memberName]. If [updateUsage] returns `true` the usage is removed from |
| /// the set. |
| void _processSet( |
| Map<String, Set<MemberUsage>> map, |
| String memberName, |
| bool Function(MemberUsage e) updateUsage, |
| ) { |
| Set<MemberUsage>? members = map[memberName]; |
| if (members == null) return; |
| // [f] might add elements to [: map[memberName] :] during the loop below |
| // so we create a new list for [: map[memberName] :] and prepend the |
| // [remaining] members after the loop. |
| map[memberName] = {}; |
| Set<MemberUsage> remaining = {}; |
| for (MemberUsage usage in members) { |
| if (!updateUsage(usage)) { |
| remaining.add(usage); |
| } |
| } |
| map[memberName]!.addAll(remaining); |
| } |
| |
| (MemberUsage, EnumSet<MemberUse>) _getMemberUsage( |
| MemberEntity member, { |
| bool checkEnqueuerConsistency = false, |
| }) { |
| EnumSet<MemberUse> useSet = EnumSet.empty(); |
| MemberUsage? usage = _memberUsage[member]; |
| if (usage == null) { |
| if (member.isInstanceMember) { |
| String memberName = member.name!; |
| ClassEntity cls = member.enclosingClass!; |
| // TODO(johnniwinther): Change this to use isNativeMember when we use |
| // CFE constants. |
| // The obvious thing to test here would be "member.isNative", |
| // however, that only works after metadata has been parsed/analyzed, |
| // and that may not have happened yet. |
| // So instead we use the enclosing class, which we know have had |
| // its metadata parsed and analyzed. |
| // Note: this assumes that there are no non-native fields on native |
| // classes, which may not be the case when a native class is subclassed. |
| bool isNative = _nativeBasicData.isNativeClass(cls); |
| usage = MemberUsage(member); |
| if (member is FieldEntity && !isNative) { |
| useSet = useSet.union(usage.init()); |
| } |
| if (!checkEnqueuerConsistency) { |
| if (member is FieldEntity && isNative) { |
| registerUsedElement(member); |
| } |
| if (member.isFunction && |
| member.name == Identifiers.call && |
| elementEnvironment.isGenericClass(cls)) { |
| _closurizedMembersWithFreeTypeVariables.add( |
| member as FunctionEntity, |
| ); |
| } |
| } |
| |
| if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) { |
| useSet = useSet.union(usage.read(Accesses.dynamicAccess)); |
| } |
| if (usage.hasPendingDynamicInvoke) { |
| Iterable<CallStructure> callStructures = _getInvocationCallStructures( |
| member, |
| ); |
| for (CallStructure callStructure in callStructures) { |
| useSet = useSet.union( |
| usage.invoke(Accesses.dynamicAccess, callStructure), |
| ); |
| if (!usage.hasPendingDynamicInvoke) { |
| break; |
| } |
| } |
| } |
| if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) { |
| useSet = useSet.union(usage.write(Accesses.dynamicAccess)); |
| } |
| |
| if (!checkEnqueuerConsistency) { |
| if (usage.hasPendingDynamicInvoke) { |
| _invokableInstanceMembersByName |
| .putIfAbsent(memberName, () => {}) |
| .add(usage); |
| } |
| if (usage.hasPendingDynamicRead) { |
| _readableInstanceMembersByName |
| .putIfAbsent(memberName, () => {}) |
| .add(usage); |
| } |
| if (usage.hasPendingDynamicWrite) { |
| _writableInstanceMembersByName |
| .putIfAbsent(memberName, () => {}) |
| .add(usage); |
| } |
| } |
| } else { |
| usage = MemberUsage(member); |
| if (member is FieldEntity) { |
| useSet = useSet.union(usage.init()); |
| } |
| } |
| if (!checkEnqueuerConsistency) { |
| _memberUsage[member] = usage; |
| } |
| } |
| return (usage, useSet); |
| } |
| |
| /// Determines whether [member] is potentially used and calls the [memberUsed] |
| /// callback on it if it is. |
| /// |
| /// [member] can be concrete or abstract and in either case potential usage |
| /// is determined by comparing the signature of [member] to the selector |
| /// structure (including arguments) at call sites. |
| void _processMemberInUsedClass( |
| ClassEntity cls, |
| MemberEntity member, |
| MemberUsedCallback memberUsed, { |
| bool checkEnqueuerConsistency = false, |
| }) { |
| if (!member.isInstanceMember) return; |
| String memberName = member.name!; |
| |
| MemberUsage? usage = _memberUsage[member]; |
| if (usage == null) { |
| final (memberUsage, useSet) = _getMemberUsage( |
| member, |
| checkEnqueuerConsistency: checkEnqueuerConsistency, |
| ); |
| usage = memberUsage; |
| if (useSet.isNotEmpty) { |
| if (checkEnqueuerConsistency) { |
| throw SpannableAssertionFailure( |
| member, |
| 'Unenqueued usage of $member: \nbefore: <none>\nafter : $usage', |
| ); |
| } else { |
| memberUsed(usage.entity, useSet); |
| } |
| } |
| } else { |
| MemberUsage original = usage; |
| if (checkEnqueuerConsistency) { |
| usage = usage.clone(); |
| } |
| if (usage.hasPendingDynamicUse) { |
| EnumSet<MemberUse> useSet = EnumSet.empty(); |
| if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) { |
| useSet = useSet.union(usage.read(Accesses.dynamicAccess)); |
| } |
| if (usage.hasPendingDynamicInvoke) { |
| Iterable<CallStructure> callStructures = _getInvocationCallStructures( |
| member, |
| ); |
| for (CallStructure callStructure in callStructures) { |
| useSet = useSet.union( |
| usage.invoke(Accesses.dynamicAccess, callStructure), |
| ); |
| if (!usage.hasPendingDynamicInvoke) { |
| break; |
| } |
| } |
| } |
| if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) { |
| useSet = useSet.union(usage.write(Accesses.dynamicAccess)); |
| } |
| if (!checkEnqueuerConsistency) { |
| if (!usage.hasPendingDynamicRead) { |
| _readableInstanceMembersByName[memberName]?.remove(usage); |
| } |
| if (!usage.hasPendingDynamicInvoke) { |
| _invokableInstanceMembersByName[memberName]?.remove(usage); |
| } |
| if (!usage.hasPendingDynamicWrite) { |
| _writableInstanceMembersByName[memberName]?.remove(usage); |
| } |
| } |
| if (checkEnqueuerConsistency && !original.dataEquals(usage)) { |
| _elementMap.reporter.internalError( |
| member, |
| 'Unenqueued usage of $member: \n' |
| 'before: $original\nafter : $usage', |
| ); |
| } |
| memberUsed(usage.entity, useSet); |
| } |
| } |
| } |
| |
| void registerUsedElement(MemberEntity element) { |
| if (element.isInstanceMember) { |
| if (element.isAbstract) { |
| _liveAbstractInstanceMembers.add(element); |
| } else { |
| _liveInstanceMembers.add(element); |
| } |
| } |
| } |
| |
| Map<ClassEntity, Set<ClassEntity>> populateHierarchyNodes() { |
| Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses = {}; |
| |
| // Updates the `isDirectlyInstantiated` and `isIndirectlyInstantiated` |
| // properties of the [ClassHierarchyNode] for [cls]. |
| |
| void addSubtypes(ClassEntity cls, InstantiationInfo info) { |
| if (!info.hasInstantiation) { |
| return; |
| } |
| classHierarchyBuilder.updateClassHierarchyNodeForClass( |
| cls, |
| directlyInstantiated: info.isDirectlyInstantiated, |
| abstractlyInstantiated: info.isAbstractlyInstantiated, |
| ); |
| |
| // Walk through the superclasses, and record the types |
| // implemented by that type on the superclasses. |
| ClassEntity? superclass = _elementMap.getSuperClass(cls); |
| while (superclass != null) { |
| Set<ClassEntity> typesImplementedBySubclassesOfCls = |
| typesImplementedBySubclasses.putIfAbsent(superclass, () => {}); |
| for (InterfaceType current in _elementMap.getSuperTypes(cls)) { |
| typesImplementedBySubclassesOfCls.add(current.element); |
| } |
| superclass = _elementMap.getSuperClass(superclass); |
| } |
| } |
| |
| // Use the [:seenClasses:] set to include non-instantiated |
| // classes: if the superclass of these classes require RTI, then |
| // they also need RTI, so that a constructor passes the type |
| // variables to the super constructor. |
| getInstantiationMap().forEach(addSubtypes); |
| |
| return typesImplementedBySubclasses; |
| } |
| |
| Iterable<MemberEntity> computeAssignedInstanceMembers() { |
| Set<MemberEntity> assignedInstanceMembers = {}; |
| for (MemberEntity instanceMember in _liveInstanceMembers) { |
| if (_hasInvokedSetter(instanceMember)) { |
| assignedInstanceMembers.add(instanceMember); |
| } |
| } |
| assignedInstanceMembers.addAll(_fieldSetters); |
| return assignedInstanceMembers; |
| } |
| |
| void registerClass(ClassEntity cls) { |
| classHierarchyBuilder.registerClass(cls); |
| } |
| |
| /// Returns `true` if [member] is inherited into a subtype of [type]. |
| /// |
| /// For instance: |
| /// |
| /// class A { m() {} } |
| /// class B extends A implements I {} |
| /// class C extends Object with A implements I {} |
| /// abstract class I { m(); } |
| /// abstract class J implements A { } |
| /// |
| /// Here `A.m` is inherited into `A`, `B`, and `C`. Because `B` and |
| /// `C` implement `I`, `isInheritedInSubtypeOf(A.m, I)` is true, but |
| /// `isInheritedInSubtypeOf(A.m, J)` is false. |
| bool isInheritedIn(MemberEntity member, ClassEntity type) { |
| // TODO(johnniwinther): Use the [member] itself to avoid enqueueing members |
| // that are overridden. |
| return isInheritedInClass(member.enclosingClass!, type); |
| } |
| |
| bool isInheritedInClass(ClassEntity memberHoldingClass, ClassEntity type) { |
| if (memberHoldingClass == _commonElements.nullClass || |
| memberHoldingClass == _commonElements.jsNullClass) { |
| // Members of `Null` and `JSNull` are always potential targets. |
| return true; |
| } |
| return classHierarchyBuilder.isInheritedInSubtypeOf( |
| memberHoldingClass, |
| type, |
| ); |
| } |
| |
| KClosedWorld closeWorld(DiagnosticReporter reporter) { |
| Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses = |
| populateHierarchyNodes(); |
| |
| BackendUsage backendUsage = _backendUsageBuilder.close(); |
| _closed = true; |
| |
| Map<MemberEntity, MemberUsage> liveMemberUsage = {}; |
| _memberUsage.forEach((MemberEntity member, MemberUsage memberUsage) { |
| if (memberUsage.hasUse) { |
| liveMemberUsage[member] = memberUsage; |
| assert( |
| _processedMembers.contains(member), |
| "Member $member is used but not processed: $memberUsage.", |
| ); |
| } else { |
| assert( |
| !_processedMembers.contains(member), |
| "Member $member is processed but not used: $memberUsage.", |
| ); |
| } |
| }); |
| for (MemberEntity member in _processedMembers) { |
| assert( |
| _memberUsage.containsKey(member), |
| "Member $member is processed but has not usage.", |
| ); |
| } |
| |
| Set<InterfaceType> instantiatedTypes = {}; |
| getInstantiationMap().forEach((_, InstantiationInfo info) { |
| if (info.instantiationMap != null) { |
| for (Set<Instance> instances in info.instantiationMap!.values) { |
| for (Instance instance in instances) { |
| instantiatedTypes.add(instance.type); |
| } |
| } |
| } |
| }); |
| |
| KClosedWorld closedWorld = KClosedWorld( |
| _elementMap, |
| options: _options, |
| elementEnvironment: elementEnvironment as KElementEnvironment, |
| dartTypes: _dartTypes, |
| commonElements: _commonElements as KCommonElements, |
| nativeData: _nativeDataBuilder.close(reporter), |
| interceptorData: _interceptorDataBuilder.close(), |
| backendUsage: backendUsage, |
| noSuchMethodData: _noSuchMethodRegistry.close(), |
| rtiNeedBuilder: _rtiNeedBuilder, |
| fieldAnalysis: _allocatorAnalysis, |
| implementedClasses: _implementedClasses, |
| liveNativeClasses: _nativeResolutionEnqueuer.liveNativeClasses, |
| liveInstanceMembers: _liveInstanceMembers, |
| liveAbstractInstanceMembers: _liveAbstractInstanceMembers, |
| assignedInstanceMembers: computeAssignedInstanceMembers(), |
| liveMemberUsage: liveMemberUsage, |
| mixinUses: classHierarchyBuilder.mixinUses, |
| typesImplementedBySubclasses: typesImplementedBySubclasses, |
| classHierarchy: classHierarchyBuilder.close(), |
| annotationsData: _annotationsDataBuilder.close(_options, reporter), |
| isChecks: _isChecks, |
| staticTypeArgumentDependencies: staticTypeArgumentDependencies, |
| dynamicTypeArgumentDependencies: dynamicTypeArgumentDependencies, |
| typeVariableTypeLiterals: typeVariableTypeLiterals, |
| genericLocalFunctions: _genericLocalFunctions, |
| closurizedMembersWithFreeTypeVariables: |
| _closurizedMembersWithFreeTypeVariables, |
| localFunctions: _localFunctions, |
| instantiatedTypes: instantiatedTypes, |
| instantiatedRecordTypes: _instantiatedRecordTypes, |
| namedTypeVariables: _namedTypeVariables, |
| ); |
| if (retainDataForTesting) { |
| _closedWorldCache = closedWorld; |
| } |
| return closedWorld; |
| } |
| } |