| // 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)'; |
| } |