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