blob: 54e73c10b6514861289345ff9fc1619b6c1bcd97 [file] [log] [blame]
// Copyright (c) 2012, 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;
import '../common/names.dart' show Identifiers, Selectors;
import '../elements/entities.dart';
import '../elements/names.dart';
import '../inferrer/abstract_value_domain.dart';
import '../util/util.dart' show Hashing, Setlet;
import 'selector.dart' show Selector;
// TODO(kasperl): This actually holds getters and setters just fine
// too and strictly they aren't functions. Maybe this needs a better
// name -- something like ElementSet seems a bit too generic.
class FunctionSet {
final Map<String, FunctionSetNode> _publicNodes;
final Map<Uri, Map<String, FunctionSetNode>> _privateNodes;
factory FunctionSet(Iterable<MemberEntity> liveInstanceMembers) {
Map<String, FunctionSetNode> publicNodes = {};
Map<Uri, Map<String, FunctionSetNode>> privateNodes = {};
for (MemberEntity member in liveInstanceMembers) {
String name = member.name!;
if (member.memberName.isPrivate) {
final uri = member.memberName.uri!;
((privateNodes[uri] ??= {})[name] ??= FunctionSetNode()).add(member);
} else {
(publicNodes[name] ??= FunctionSetNode()).add(member);
}
}
return FunctionSet.internal(publicNodes, privateNodes);
}
FunctionSet.internal(this._publicNodes, this._privateNodes);
/// Returns all the functions that may be invoked with the [selector] on a
/// receiver with the given [constraint]. The returned elements may include
/// noSuchMethod handlers that are potential targets indirectly through the
/// noSuchMethod mechanism.
Iterable<MemberEntity> filter(
Selector selector,
AbstractValue? receiver,
AbstractValueDomain domain,
) {
return query(selector, receiver, domain).functions;
}
SelectorMask _createSelectorMask(
Selector selector,
AbstractValue? receiver,
AbstractValueDomain domain,
) {
return receiver != null
? SelectorMask(selector, receiver)
: SelectorMask(selector, domain.dynamicType);
}
/// Returns the set of functions that can be the target of a call to
/// [selector] on a receiver constrained by [constraint] including
/// 'noSuchMethod' methods where applicable.
FunctionSetQuery query(
Selector selector,
AbstractValue? receiver,
AbstractValueDomain domain,
) {
Name name = selector.memberName;
SelectorMask selectorMask = _createSelectorMask(selector, receiver, domain);
SelectorMask noSuchMethodMask = SelectorMask(
Selectors.noSuchMethod_,
selectorMask.receiver,
);
FunctionSetNode? node;
if (name.isPrivate) {
final forUri = _privateNodes[name.uri];
if (forUri != null) node = forUri[name.text];
} else {
node = _publicNodes[name.text];
}
FunctionSetNode? noSuchMethods = _publicNodes[Identifiers.noSuchMethod_];
if (node != null) {
return node.query(selectorMask, domain, noSuchMethods, noSuchMethodMask);
}
// If there is no method that matches [selector] we know we can
// only hit [:noSuchMethod:].
if (noSuchMethods == null) {
return const EmptyFunctionSetQuery();
}
return noSuchMethods.query(noSuchMethodMask, domain);
}
}
/// A selector/constraint pair representing the dynamic invocation of [selector]
/// on a receiver constrained by [constraint].
class SelectorMask {
final Selector selector;
final AbstractValue receiver;
@override
final int hashCode;
SelectorMask(this.selector, this.receiver)
: hashCode = Hashing.mixHashCodeBits(selector.hashCode, receiver.hashCode);
String get name => selector.name;
/// Check that the mask applies to the element. Skip checking the name since
/// this is called from [FunctionSet.query] which implicitly checks the name
/// via the Map lookups.
bool _applies(MemberEntity element, AbstractValueDomain domain) {
if (!selector.appliesStructural(element)) {
return false;
}
return domain
.isTargetingMember(receiver, element, selector.memberName)
.isPotentiallyTrue;
}
bool needsNoSuchMethodHandling(AbstractValueDomain domain) {
return domain
.needsNoSuchMethodHandling(receiver, selector)
.isPotentiallyTrue;
}
@override
bool operator ==(other) {
if (identical(this, other)) return true;
return other is SelectorMask &&
selector == other.selector &&
receiver == other.receiver;
}
@override
String toString() => '($selector,$receiver)';
}
/// A node in the [FunctionSet] caching all [FunctionSetQuery] object for
/// selectors with the same [name].
class FunctionSetNode {
final Map<SelectorMask, FunctionSetQuery> cache = {};
// Initially, we keep the elements in a list because it is more
// compact than a hash set. Once we get enough elements, we change
// the representation to be a set to get faster contains checks.
static const int maxElementsInList = 8;
Iterable<MemberEntity> elements = [];
bool isList = true;
FunctionSetNode();
void add(MemberEntity element) {
// We try to avoid clearing the cache unless we have to. For that
// reason we keep the explicit contains check even though the add
// method ends up doing the work again (for sets).
if (!elements.contains(element)) {
if (isList && elements.length >= maxElementsInList) {
elements = elements.toSet();
isList = false;
}
if (isList) {
final list = elements as List<MemberEntity>;
list.add(element);
} else {
final set = elements as Set<MemberEntity>;
set.add(element);
}
if (cache.isNotEmpty) cache.clear();
}
}
void remove(MemberEntity element) {
if (isList) {
final list = elements as List<MemberEntity>;
int index = list.indexOf(element);
if (index < 0) return;
MemberEntity last = list.removeLast();
if (index != list.length) {
list[index] = last;
}
if (cache.isNotEmpty) cache.clear();
} else {
final set = elements as List<MemberEntity>;
if (set.remove(element)) {
// To avoid wobbling between the two representations, we do
// not transition back to the list representation even if we
// end up with few enough elements at this point.
if (cache.isNotEmpty) cache.clear();
}
}
}
bool contains(MemberEntity element) {
return elements.contains(element);
}
void forEach(void Function(MemberEntity member) action) {
elements.forEach(action);
}
/// Returns the set of functions that can be the target of [selectorMask]
/// including no such method handling where applicable.
FunctionSetQuery query(
SelectorMask selectorMask,
AbstractValueDomain domain, [
FunctionSetNode? noSuchMethods,
SelectorMask? noSuchMethodMask,
]) {
FunctionSetQuery? result = cache[selectorMask];
if (result != null) return result;
Setlet<MemberEntity>? functions;
for (MemberEntity element in elements) {
if (selectorMask._applies(element, domain)) {
// Defer the allocation of the functions set until we are
// sure we need it. This allows us to return immutable empty
// lists when the filtering produced no results.
functions ??= Setlet();
functions.add(element);
}
}
// If we cannot ensure a method will be found at runtime, we also
// add [noSuchMethod] implementations that apply to [mask] as
// potential targets.
if (noSuchMethods != null &&
selectorMask.needsNoSuchMethodHandling(domain)) {
// If [noSuchMethods] was provided then [noSuchMethodMask] should also
// have been provided.
FunctionSetQuery noSuchMethodQuery = noSuchMethods.query(
noSuchMethodMask!,
domain,
);
if (noSuchMethodQuery.functions.isNotEmpty) {
functions ??= Setlet();
functions.addAll(noSuchMethodQuery.functions);
}
}
cache[selectorMask] = result = (functions != null)
? FullFunctionSetQuery(functions)
: const EmptyFunctionSetQuery();
return result;
}
@override
String toString() {
StringBuffer sb = StringBuffer();
sb.write('FunctionSetNode(');
String comma = '';
cache.forEach((mask, query) {
sb.write(comma);
sb.write('$mask=$query');
comma = ',';
});
sb.write(')');
return sb.toString();
}
}
/// A set of functions that are the potential targets of all call sites sharing
/// the same receiver mask and selector.
abstract class FunctionSetQuery {
const FunctionSetQuery();
/// Returns all potential targets of this function set.
Iterable<MemberEntity> get functions;
}
class EmptyFunctionSetQuery implements FunctionSetQuery {
const EmptyFunctionSetQuery();
@override
Iterable<MemberEntity> get functions => const [];
@override
String toString() => '<empty>';
}
class FullFunctionSetQuery implements FunctionSetQuery {
@override
final Iterable<MemberEntity> functions;
FullFunctionSetQuery(this.functions);
@override
String toString() => 'FunctionSetQuery($functions)';
}