blob: 869e7872eabce10f7275e3c01cc755358e268fbb [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/resolution.dart' show Resolution;
import '../common_elements.dart' show CommonElements, ElementEnvironment;
import '../elements/elements.dart' show MemberElement;
import '../elements/entities.dart';
import '../elements/types.dart';
import '../js/js.dart' as jsAst;
import '../types/types.dart' show TypeMask;
import '../universe/selector.dart';
import '../world.dart' show ClosedWorld;
import 'backend_helpers.dart';
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, TypeMask mask);
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);
/// 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);
}
abstract class InterceptorDataBuilder {
void addInterceptors(ClassEntity cls);
void addInterceptorsForNativeClassMembers(ClassEntity cls);
InterceptorData onResolutionComplete(ClosedWorld closedWorld);
}
class InterceptorDataImpl implements InterceptorData {
final NativeBasicData _nativeData;
final BackendHelpers _helpers;
final ClosedWorld _closedWorld;
/// 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;
/// 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._helpers,
this._closedWorld,
this._interceptedElements,
this._interceptedClasses,
this._classesMixedIntoInterceptedClasses);
bool isInterceptedMethod(MemberElement element) {
if (!element.isInstanceMember) return false;
if (element.isGenerativeConstructorBody) {
return _nativeData.isNativeOrExtendsNative(element.enclosingClass);
}
return _interceptedElements[element.name] != null;
}
bool fieldHasInterceptedGetter(FieldEntity element) {
return _interceptedElements[element.name] != null;
}
bool fieldHasInterceptedSetter(FieldEntity element) {
return _interceptedElements[element.name] != null;
}
bool isInterceptedName(String name) {
return _interceptedElements[name] != null;
}
bool isInterceptedSelector(Selector selector) {
return _interceptedElements[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, TypeMask mask) {
Set<MemberEntity> elements =
_interceptedMixinElements.putIfAbsent(selector.name, () {
Set<MemberEntity> elements = _interceptedElements[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 || mask.canHit(element, selector, _closedWorld));
});
}
/// True if the given class is an internal class used for type inference
/// and never exists at runtime.
bool _isCompileTimeOnlyClass(ClassEntity class_) {
return class_ == _helpers.jsPositiveIntClass ||
class_ == _helpers.jsUInt32Class ||
class_ == _helpers.jsUInt31Class ||
class_ == _helpers.jsFixedArrayClass ||
class_ == _helpers.jsUnmodifiableArrayClass ||
class_ == _helpers.jsMutableArrayClass ||
class_ == _helpers.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) {
Set<MemberEntity> intercepted = _interceptedElements[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);
if (nativeSubclasses != null) result.addAll(nativeSubclasses);
}
}
return result;
});
}
Set<ClassEntity> nativeSubclassesOfMixin(ClassEntity mixin) {
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);
Iterable<ClassEntity> get interceptedClasses => _interceptedClasses;
bool mayGenerateInstanceofCheck(DartType type) {
// 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;
InterfaceType interfaceType = type;
ClassEntity classElement = interfaceType.element;
if (isInterceptedClass(classElement)) return false;
return _closedWorld.hasOnlySubclasses(classElement);
}
}
class InterceptorDataBuilderImpl implements InterceptorDataBuilder {
final NativeBasicData _nativeData;
final BackendHelpers _helpers;
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._helpers,
this._elementEnvironment, this._commonElements);
InterceptorData onResolutionComplete(ClosedWorld closedWorld) {
return new InterceptorDataImpl(
_nativeData,
_helpers,
closedWorld,
_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(_helpers.jsInterceptorClass);
}
}
class OneShotInterceptorData {
final InterceptorData _interceptorData;
final BackendHelpers _helpers;
OneShotInterceptorData(this._interceptorData, this._helpers);
/// 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) {
Set<ClassEntity> classes =
_interceptorData.getInterceptedClassesOn(selector.name);
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(_helpers.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;
}
}
}