blob: 3ef09f347ead03c5a53c0a0ffc8908537e5d62c3 [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.
library js_backend.interceptor_data;
import '../common/names.dart' show Identifiers;
import '../common_elements.dart' show CommonElements, ElementEnvironment;
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.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.hasOnlySubclasses(classElement);
}
}
class InterceptorDataBuilderImpl implements InterceptorDataBuilder {
final NativeBasicData _nativeData;
final ElementEnvironment _elementEnvironment;
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>> _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.putIfAbsent(
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.putIfAbsent(
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;
}
}
}