| // 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. | |
| library js_backend.interceptor_data; | |
| import '../common/names.dart' show Identifiers; | |
| import '../common_elements.dart' | |
| show CommonElements, KCommonElements, KElementEnvironment; | |
| import '../elements/entities.dart'; | |
| import '../elements/types.dart'; | |
| import '../js/js.dart' as jsAst; | |
| import '../types/abstract_value_domain.dart'; | |
| import '../universe/selector.dart'; | |
| import '../world.dart' show JClosedWorld; | |
| import 'namer.dart'; | |
| import 'native_data.dart'; | |
| abstract class InterceptorData { | |
| /// Returns `true` if [cls] is an intercepted class. | |
| bool isInterceptedClass(ClassEntity element); | |
| bool isInterceptedMethod(MemberEntity element); | |
| bool fieldHasInterceptedGetter(FieldEntity element); | |
| bool fieldHasInterceptedSetter(FieldEntity element); | |
| bool isInterceptedName(String name); | |
| bool isInterceptedSelector(Selector selector); | |
| bool isInterceptedMixinSelector( | |
| Selector selector, AbstractValue mask, JClosedWorld closedWorld); | |
| Iterable<ClassEntity> get interceptedClasses; | |
| bool isMixedIntoInterceptedClass(ClassEntity element); | |
| /// Returns a set of interceptor classes that contain a member named [name] | |
| /// | |
| /// Returns an empty set if there is no class. Do not modify the returned set. | |
| Set<ClassEntity> getInterceptedClassesOn( | |
| String name, JClosedWorld closedWorld); | |
| /// Whether the compiler can use the native `instanceof` check to test for | |
| /// instances of [type]. This is true for types that are not used as mixins or | |
| /// interfaces. | |
| bool mayGenerateInstanceofCheck(DartType type, JClosedWorld closedWorld); | |
| } | |
| abstract class InterceptorDataBuilder { | |
| void addInterceptors(ClassEntity cls); | |
| void addInterceptorsForNativeClassMembers(ClassEntity cls); | |
| InterceptorData close(); | |
| } | |
| class InterceptorDataImpl implements InterceptorData { | |
| final NativeBasicData _nativeData; | |
| final CommonElements _commonElements; | |
| /// The members of instantiated interceptor classes: maps a member name to the | |
| /// list of members that have that name. This map is used by the codegen to | |
| /// know whether a send must be intercepted or not. | |
| final Map<String, Set<MemberEntity>> interceptedMembers; | |
| /// Set of classes whose methods are intercepted. | |
| final Set<ClassEntity> interceptedClasses; | |
| /// Set of classes used as mixins on intercepted (native and primitive) | |
| /// classes. Methods on these classes might also be mixed in to regular Dart | |
| /// (unintercepted) classes. | |
| final Set<ClassEntity> classesMixedIntoInterceptedClasses; | |
| /// The members of mixin classes that are mixed into an instantiated | |
| /// interceptor class. This is a cached subset of [_interceptedElements]. | |
| /// | |
| /// Mixin methods are not specialized for the class they are mixed into. | |
| /// Methods mixed into intercepted classes thus always make use of the | |
| /// explicit receiver argument, even when mixed into non-interceptor classes. | |
| /// | |
| /// These members must be invoked with a correct explicit receiver even when | |
| /// the receiver is not an intercepted class. | |
| final Map<String, Set<MemberEntity>> _interceptedMixinElements = | |
| new Map<String, Set<MemberEntity>>(); | |
| final Map<String, Set<ClassEntity>> _interceptedClassesCache = | |
| new Map<String, Set<ClassEntity>>(); | |
| final Set<ClassEntity> _noClasses = new Set<ClassEntity>(); | |
| InterceptorDataImpl( | |
| this._nativeData, | |
| this._commonElements, | |
| this.interceptedMembers, | |
| this.interceptedClasses, | |
| this.classesMixedIntoInterceptedClasses); | |
| bool isInterceptedMethod(MemberEntity element) { | |
| if (!element.isInstanceMember) return false; | |
| // TODO(johnniwinther): Avoid this hack. | |
| if (element is ConstructorBodyEntity) { | |
| return _nativeData.isNativeOrExtendsNative(element.enclosingClass); | |
| } | |
| return interceptedMembers[element.name] != null; | |
| } | |
| bool fieldHasInterceptedGetter(FieldEntity element) { | |
| return interceptedMembers[element.name] != null; | |
| } | |
| bool fieldHasInterceptedSetter(FieldEntity element) { | |
| return interceptedMembers[element.name] != null; | |
| } | |
| bool isInterceptedName(String name) { | |
| return interceptedMembers[name] != null; | |
| } | |
| bool isInterceptedSelector(Selector selector) { | |
| return interceptedMembers[selector.name] != null; | |
| } | |
| /// Returns `true` iff [selector] matches an element defined in a class mixed | |
| /// into an intercepted class. These selectors are not eligible for the | |
| /// 'dummy explicit receiver' optimization. | |
| bool isInterceptedMixinSelector( | |
| Selector selector, AbstractValue mask, JClosedWorld closedWorld) { | |
| Set<MemberEntity> elements = | |
| _interceptedMixinElements.putIfAbsent(selector.name, () { | |
| Set<MemberEntity> elements = interceptedMembers[selector.name]; | |
| if (elements == null) return null; | |
| return elements | |
| .where((element) => classesMixedIntoInterceptedClasses | |
| .contains(element.enclosingClass)) | |
| .toSet(); | |
| }); | |
| if (elements == null) return false; | |
| if (elements.isEmpty) return false; | |
| return elements.any((element) { | |
| return selector.applies(element) && | |
| (mask == null || | |
| closedWorld.abstractValueDomain.canHit(mask, element, selector)); | |
| }); | |
| } | |
| /// True if the given class is an internal class used for type inference | |
| /// and never exists at runtime. | |
| bool _isCompileTimeOnlyClass(ClassEntity class_) { | |
| return class_ == _commonElements.jsPositiveIntClass || | |
| class_ == _commonElements.jsUInt32Class || | |
| class_ == _commonElements.jsUInt31Class || | |
| class_ == _commonElements.jsFixedArrayClass || | |
| class_ == _commonElements.jsUnmodifiableArrayClass || | |
| class_ == _commonElements.jsMutableArrayClass || | |
| class_ == _commonElements.jsExtendableArrayClass; | |
| } | |
| /// Returns a set of interceptor classes that contain a member named [name] | |
| /// | |
| /// Returns an empty set if there is no class. Do not modify the returned set. | |
| Set<ClassEntity> getInterceptedClassesOn( | |
| String name, JClosedWorld closedWorld) { | |
| Set<MemberEntity> intercepted = interceptedMembers[name]; | |
| if (intercepted == null) return _noClasses; | |
| return _interceptedClassesCache.putIfAbsent(name, () { | |
| // Populate the cache by running through all the elements and | |
| // determine if the given selector applies to them. | |
| Set<ClassEntity> result = new Set<ClassEntity>(); | |
| for (MemberEntity element in intercepted) { | |
| ClassEntity classElement = element.enclosingClass; | |
| if (_isCompileTimeOnlyClass(classElement)) continue; | |
| if (_nativeData.isNativeOrExtendsNative(classElement) || | |
| interceptedClasses.contains(classElement)) { | |
| result.add(classElement); | |
| } | |
| if (classesMixedIntoInterceptedClasses.contains(classElement)) { | |
| Set<ClassEntity> nativeSubclasses = | |
| nativeSubclassesOfMixin(classElement, closedWorld); | |
| if (nativeSubclasses != null) result.addAll(nativeSubclasses); | |
| } | |
| } | |
| return result; | |
| }); | |
| } | |
| Set<ClassEntity> nativeSubclassesOfMixin( | |
| ClassEntity mixin, JClosedWorld closedWorld) { | |
| Iterable<ClassEntity> uses = closedWorld.mixinUsesOf(mixin); | |
| Set<ClassEntity> result = null; | |
| for (ClassEntity use in uses) { | |
| closedWorld.classHierarchy.forEachStrictSubclassOf(use, | |
| (ClassEntity subclass) { | |
| if (_nativeData.isNativeOrExtendsNative(subclass)) { | |
| if (result == null) result = new Set<ClassEntity>(); | |
| result.add(subclass); | |
| } | |
| }); | |
| } | |
| return result; | |
| } | |
| bool isInterceptedClass(ClassEntity element) { | |
| if (element == null) return false; | |
| if (_nativeData.isNativeOrExtendsNative(element)) return true; | |
| if (interceptedClasses.contains(element)) return true; | |
| if (classesMixedIntoInterceptedClasses.contains(element)) return true; | |
| return false; | |
| } | |
| bool isMixedIntoInterceptedClass(ClassEntity element) => | |
| classesMixedIntoInterceptedClasses.contains(element); | |
| bool mayGenerateInstanceofCheck(DartType type, JClosedWorld closedWorld) { | |
| // We can use an instanceof check for raw types that have no subclass that | |
| // is mixed-in or in an implements clause. | |
| if (!type.treatAsRaw) return false; | |
| if (type.isFutureOr) return false; | |
| InterfaceType interfaceType = type; | |
| ClassEntity classElement = interfaceType.element; | |
| if (isInterceptedClass(classElement)) return false; | |
| return closedWorld.classHierarchy.hasOnlySubclasses(classElement); | |
| } | |
| } | |
| class InterceptorDataBuilderImpl implements InterceptorDataBuilder { | |
| final NativeBasicData _nativeData; | |
| final KElementEnvironment _elementEnvironment; | |
| final KCommonElements _commonElements; | |
| /// The members of instantiated interceptor classes: maps a member name to the | |
| /// list of members that have that name. This map is used by the codegen to | |
| /// know whether a send must be intercepted or not. | |
| final Map<String, Set<MemberEntity>> _interceptedElements = | |
| <String, Set<MemberEntity>>{}; | |
| /// Set of classes whose methods are intercepted. | |
| final Set<ClassEntity> _interceptedClasses = new Set<ClassEntity>(); | |
| /// Set of classes used as mixins on intercepted (native and primitive) | |
| /// classes. Methods on these classes might also be mixed in to regular Dart | |
| /// (unintercepted) classes. | |
| final Set<ClassEntity> _classesMixedIntoInterceptedClasses = | |
| new Set<ClassEntity>(); | |
| InterceptorDataBuilderImpl( | |
| this._nativeData, this._elementEnvironment, this._commonElements); | |
| InterceptorData close() { | |
| return new InterceptorDataImpl( | |
| _nativeData, | |
| _commonElements, | |
| _interceptedElements, | |
| _interceptedClasses, | |
| _classesMixedIntoInterceptedClasses); | |
| } | |
| void addInterceptorsForNativeClassMembers(ClassEntity cls) { | |
| _elementEnvironment.forEachClassMember(cls, | |
| (ClassEntity cls, MemberEntity member) { | |
| if (member.name == Identifiers.call) return; | |
| // All methods on [Object] are shadowed by [Interceptor]. | |
| if (cls == _commonElements.objectClass) return; | |
| Set<MemberEntity> set = | |
| _interceptedElements[member.name] ??= new Set<MemberEntity>(); | |
| set.add(member); | |
| }); | |
| // Walk superclass chain to find mixins. | |
| _elementEnvironment.forEachMixin(cls, (ClassEntity mixin) { | |
| _classesMixedIntoInterceptedClasses.add(mixin); | |
| }); | |
| } | |
| void addInterceptors(ClassEntity cls) { | |
| if (_interceptedClasses.add(cls)) { | |
| _elementEnvironment.forEachClassMember(cls, | |
| (ClassEntity cls, MemberEntity member) { | |
| // All methods on [Object] are shadowed by [Interceptor]. | |
| if (cls == _commonElements.objectClass) return; | |
| Set<MemberEntity> set = | |
| _interceptedElements[member.name] ??= new Set<MemberEntity>(); | |
| set.add(member); | |
| }); | |
| } | |
| _interceptedClasses.add(_commonElements.jsInterceptorClass); | |
| } | |
| } | |
| class OneShotInterceptorData { | |
| final InterceptorData _interceptorData; | |
| final CommonElements _commonElements; | |
| OneShotInterceptorData(this._interceptorData, this._commonElements); | |
| /// A collection of selectors that must have a one shot interceptor generated. | |
| final Map<jsAst.Name, Selector> _oneShotInterceptors = | |
| <jsAst.Name, Selector>{}; | |
| Selector getOneShotInterceptorSelector(jsAst.Name name) => | |
| _oneShotInterceptors[name]; | |
| Iterable<jsAst.Name> get oneShotInterceptorNames => | |
| _oneShotInterceptors.keys.toList()..sort(); | |
| /// A map of specialized versions of the [getInterceptorMethod]. | |
| /// | |
| /// Since [getInterceptorMethod] is a hot method at runtime, we're always | |
| /// specializing it based on the incoming type. The keys in the map are the | |
| /// names of these specialized versions. Note that the generic version that | |
| /// contains all possible type checks is also stored in this map. | |
| final Map<jsAst.Name, Set<ClassEntity>> _specializedGetInterceptors = | |
| <jsAst.Name, Set<ClassEntity>>{}; | |
| Iterable<jsAst.Name> get specializedGetInterceptorNames => | |
| _specializedGetInterceptors.keys.toList()..sort(); | |
| Set<ClassEntity> getSpecializedGetInterceptorsFor(jsAst.Name name) => | |
| _specializedGetInterceptors[name]; | |
| jsAst.Name registerOneShotInterceptor( | |
| Selector selector, Namer namer, JClosedWorld closedWorld) { | |
| Set<ClassEntity> classes = | |
| _interceptorData.getInterceptedClassesOn(selector.name, closedWorld); | |
| jsAst.Name name = namer.nameForGetOneShotInterceptor(selector, classes); | |
| if (!_oneShotInterceptors.containsKey(name)) { | |
| registerSpecializedGetInterceptor(classes, namer); | |
| _oneShotInterceptors[name] = selector; | |
| } | |
| return name; | |
| } | |
| void registerSpecializedGetInterceptor( | |
| Set<ClassEntity> classes, Namer namer) { | |
| jsAst.Name name = namer.nameForGetInterceptor(classes); | |
| if (classes.contains(_commonElements.jsInterceptorClass)) { | |
| // We can't use a specialized [getInterceptorMethod], so we make | |
| // sure we emit the one with all checks. | |
| _specializedGetInterceptors[name] = _interceptorData.interceptedClasses; | |
| } else { | |
| _specializedGetInterceptors[name] = classes; | |
| } | |
| } | |
| } |