[dart2js] Add universe/member_hierarchy.dart

This will be used from DynamicCallSiteTypeInformation nodes to get a list of receivers for dynamic calls. The InferrerEngine will also use it to calculate overrides of a given function and mark them as called.

Change-Id: I5738e393ae2de4cb93fd55c60e944a2db067b448
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/266422
Reviewed-by: Mayank Patke <fishythefish@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
diff --git a/pkg/compiler/lib/src/call_graph/applies_to.dart b/pkg/compiler/lib/src/call_graph/applies_to.dart
deleted file mode 100644
index e2ce0a8..0000000
--- a/pkg/compiler/lib/src/call_graph/applies_to.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2022, 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.
-
-import 'package:compiler/src/common/names.dart';
-import 'package:compiler/src/elements/entities.dart';
-import 'package:compiler/src/inferrer/abstract_value_domain.dart';
-import 'package:compiler/src/universe/selector.dart';
-
-import '../js_model/js_world.dart';
-
-class MemberAppliesTo {
-  final MemberEntity member;
-  AbstractValue mask;
-
-  MemberAppliesTo(this.member, this.mask);
-
-  @override
-  String toString() => 'MemberAppliesTo($member:$mask)';
-}
-
-class MemberAppliesToBuilder {
-  final JClosedWorld _closedWorld;
-  final Map<Selector, Iterable<MemberAppliesTo>> _memberSetsBySelector = {};
-  final Map<Selector, List<MemberEntity>> _membersBySelector = {};
-
-  MemberAppliesToBuilder(this._closedWorld) {
-    for (final member in _closedWorld.liveInstanceMembers) {
-      if (member.isFunction || member.isGetter || member.isSetter) {
-        (_membersBySelector[Selector.fromElement(member)] ??= []).add(member);
-      }
-    }
-  }
-
-  Iterable<MemberAppliesTo> _buildSets(Selector selector) {
-    final List<MemberAppliesTo> memberSets = [];
-    final members = _membersBySelector.remove(selector);
-    if (members == null) {
-      return selector == Selectors.noSuchMethod_
-          ? const []
-          : forSelector(Selectors.noSuchMethod_);
-    }
-    for (final member in members) {
-      // TODO(fishythefish): Use type cone mask here.
-      final mask = _closedWorld.abstractValueDomain
-          .createNonNullSubclass(member.enclosingClass!);
-      final memberSet = MemberAppliesTo(member, mask);
-      memberSets.add(memberSet);
-    }
-    return memberSets;
-  }
-
-  Iterable<MemberAppliesTo> forSelector(Selector selector) =>
-      _memberSetsBySelector[selector] ??= _buildSets(selector);
-}
diff --git a/pkg/compiler/lib/src/ir/element_map.dart b/pkg/compiler/lib/src/ir/element_map.dart
index 461b8d4..c1c8361 100644
--- a/pkg/compiler/lib/src/ir/element_map.dart
+++ b/pkg/compiler/lib/src/ir/element_map.dart
@@ -63,6 +63,8 @@
   InterfaceType getThisType(covariant ClassEntity cls);
   InterfaceType? getSuperType(covariant ClassEntity cls);
   OrderedTypeSet getOrderedTypeSet(covariant ClassEntity cls);
+
+  /// Returns the [ClassEntity] objects for interfaces that [cls] `implements`.
   Iterable<InterfaceType> getInterfaces(covariant ClassEntity cls);
   InterfaceType? asInstanceOf(InterfaceType type, ClassEntity cls);
   DartType substByContext(DartType type, InterfaceType context);
diff --git a/pkg/compiler/lib/src/js_model/element_map.dart b/pkg/compiler/lib/src/js_model/element_map.dart
index 5ec1d3f2..8b79e54 100644
--- a/pkg/compiler/lib/src/js_model/element_map.dart
+++ b/pkg/compiler/lib/src/js_model/element_map.dart
@@ -8,6 +8,7 @@
 import '../common/elements.dart' show JCommonElements, JElementEnvironment;
 import '../constants/values.dart';
 import '../elements/entities.dart';
+import '../elements/indexed.dart';
 import '../elements/jumps.dart';
 import '../elements/names.dart';
 import '../elements/types.dart';
@@ -40,6 +41,8 @@
   /// Returns the [InterfaceType] corresponding to [type].
   InterfaceType getInterfaceType(ir.InterfaceType type);
 
+  Iterable<InterfaceType> getInterfaces(IndexedClass cls);
+
   /// Returns the [TypeVariableType] corresponding to [type].
   TypeVariableType getTypeVariableType(ir.TypeParameterType type);
 
diff --git a/pkg/compiler/lib/src/universe/member_hierarchy.dart b/pkg/compiler/lib/src/universe/member_hierarchy.dart
new file mode 100644
index 0000000..4d2fd8a
--- /dev/null
+++ b/pkg/compiler/lib/src/universe/member_hierarchy.dart
@@ -0,0 +1,344 @@
+// Copyright (c) 2022, 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.
+
+import 'dart:collection';
+
+import 'package:compiler/src/common/names.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/elements/indexed.dart';
+import 'package:compiler/src/elements/names.dart';
+import 'package:compiler/src/inferrer/abstract_value_domain.dart';
+import 'package:compiler/src/js_model/js_world.dart';
+import 'package:compiler/src/universe/call_structure.dart';
+import 'package:compiler/src/universe/function_set.dart';
+import 'package:compiler/src/universe/selector.dart';
+
+class _QueuedMember {
+  /// The member to be processed.
+  final MemberEntity member;
+
+  /// The class we want to look for ancestors of [member] from. This might be
+  /// a supertype of member's enclosing class or a mixin application.
+  final ClassEntity cls;
+
+  /// Whether or not [cls] is the declarer of [member]. In most cases this is
+  /// true when [cls] equals member's [MemberEntity.enclosingClass]. However
+  /// for mixins this can be true when [cls] is the mixin application class.
+  final bool isDeclaration;
+
+  _QueuedMember(this.member, this.cls, {required this.isDeclaration});
+}
+
+class MemberHierarchyBuilder {
+  final JClosedWorld closedWorld;
+  final Map<SelectorMask, Iterable<MemberEntity>> _callCache = {};
+  final Map<Selector, Set<MemberEntity>> _selectorRoots = {};
+  final Map<MemberEntity, Set<MemberEntity>> _overrides = {};
+
+  MemberHierarchyBuilder(this.closedWorld);
+
+  /// Uses [visited] to detect and avoid cycles in the override graph.
+  void _forEachOverride(MemberEntity member,
+      void Function(MemberEntity override) f, Set<MemberEntity> visited) {
+    final overrides = _overrides[member];
+    if (overrides == null) return;
+    for (final override in overrides) {
+      if (!visited.add(override)) continue;
+      f(override);
+      _forEachOverride(override, f, visited);
+    }
+  }
+
+  /// Recursively applies [f] to each override of [entity].
+  void forEachOverride(
+      MemberEntity entity, void Function(MemberEntity override) f) {
+    _forEachOverride(entity, f, {});
+  }
+
+  /// Returns the set of root member declarations for [selector]. [cls] acts as
+  /// a subclass filter for these roots.
+  ///
+  /// The set of roots is the smallest set of members that are an ancestor of
+  /// every implementation of [selector]. This is with the caveat that we
+  /// consider each mixin application to declare a "copy" of each member of that
+  /// mixin. They share the same [MemberEntity] but each copy is considered a
+  /// separate potential root.
+  Iterable<MemberEntity> rootsForSelector(ClassEntity cls, Selector selector) {
+    final roots = _selectorRoots[_normalizeSelector(selector)];
+    if (roots == null) return const [];
+    final classHierarchy = closedWorld.classHierarchy;
+    return roots
+        .where((r) =>
+            selector.appliesStructural(r) &&
+            classHierarchy.isSubclassOf(r.enclosingClass!, cls))
+        .toSet();
+  }
+
+  /// Returns a set of common ancestors (not necessarily the smallest) for all
+  /// possible targets when calling [selector] on an object with type
+  /// [receiverType].
+  ///
+  /// Caches results for each receiver/selector pair. If [receiverType] is
+  /// `null` the receiver is considered dynamic and any member that matches
+  /// [selector] is a possible target. Also adds relevant [noSuchMethod] and
+  /// `null` targets if needed for the call.
+  Iterable<MemberEntity> rootsForCall(
+      AbstractValue? receiverType, Selector selector) {
+    final domain = closedWorld.abstractValueDomain;
+    receiverType ??= domain.dynamicType;
+    final selectorMask = SelectorMask(selector, receiverType);
+    final cachedResult = _callCache[selectorMask];
+    if (cachedResult != null) return cachedResult;
+
+    Iterable<MemberEntity> targetsForReceiver = const [];
+
+    // TODO(natebiggs): Can we calculate this as part of the above call to
+    // findRootsOfTargets?
+    final needsNoSuchMethod = selector != Selectors.noSuchMethod_ &&
+        domain
+            .needsNoSuchMethodHandling(receiverType, selector)
+            .isPotentiallyTrue;
+
+    final isNull = domain.isNull(receiverType);
+
+    if (isNull.isPotentiallyTrue) {
+      // Add relevant member if null is a potential target.
+      final nullMatch = closedWorld.elementEnvironment.lookupLocalClassMember(
+          closedWorld.commonElements.jsNullClass, selector.memberName);
+      if (nullMatch != null) {
+        targetsForReceiver = {...targetsForReceiver, nullMatch};
+      }
+    }
+
+    final result = needsNoSuchMethod
+        ? targetsForReceiver
+            .followedBy(rootsForCall(receiverType, Selectors.noSuchMethod_))
+            .toSet()
+        : targetsForReceiver;
+
+    return _callCache[selectorMask] = result;
+  }
+
+  /// Map call selectors to a getter with the same name. This allows us to
+  /// ignore their call structure since overridden methods can have different
+  /// call structures. This also handles method tear-offs.
+  static Selector _normalizeSelector(Selector selector) =>
+      selector.isCall ? Selector.getter(selector.memberName) : selector;
+
+  static bool _skipMemberInternal(MemberEntity member) {
+    return member.isStatic;
+  }
+
+  static bool skipMember(MemberEntity member, Selector selector) {
+    return _skipMemberInternal(member) || !selector.appliesUnnamed(member);
+  }
+
+  static List<Selector> _selectorsForMember(MemberEntity member) {
+    final List<Selector> result = [];
+    if (member is FieldEntity) {
+      // A field implicitly defines both a getter and setter. Each of these can
+      // have different overrides so we consider them separately.
+      result.add(Selector.getter(member.memberName));
+      result.add(Selector.setter(member.memberName));
+    } else {
+      final selector = Selector.fromElement(member);
+      result.add(_normalizeSelector(selector));
+    }
+    return result;
+  }
+
+  void _processNext(
+      Queue<_QueuedMember> queue,
+      Selector selector,
+      Map<ClassEntity, MemberEntity?> roots,
+      void Function(MemberEntity parent, MemberEntity override) join) {
+    final state = queue.removeFirst();
+    if (state.isDeclaration && !roots.containsKey(state.cls)) {
+      // Only add the potential root if we're processing a declaration of
+      // the member and have not already found an override for the class'
+      // declaration.
+      roots[state.cls] = state.member;
+    }
+    final elementEnv = closedWorld.elementEnvironment;
+    final elementMap = closedWorld.elementMap;
+    final member = state.member;
+    final cls = state.cls;
+    final name = selector.memberName;
+
+    void addParent(
+        MemberEntity parent, ClassEntity parentCls, bool fromSuperclass) {
+      if ((_overrides[parent] ??= {}).add(member)) {
+        join(parent, member);
+      }
+
+      // Both the class we are considering and the enclosing class of [member]
+      // are not roots since we found an override for them.
+      roots[cls] = null;
+      roots[member.enclosingClass!] = null;
+
+      // Enqueue parent to continue processing from there. parentCls can be a
+      // mixin application since we consider these to have duplicates of the
+      // mixin's members.
+      queue.add(_QueuedMember(parent, parentCls, isDeclaration: true));
+    }
+
+    void processInterfaces(IndexedClass toProcess) {
+      final interfaces = elementMap.getInterfaces(toProcess);
+      for (final interface in interfaces) {
+        final interfaceCls = interface.element;
+        // For unnamed mixin applications, members can be owned by the
+        // underlying mixin which is also an interface for that same
+        // application. This would cause a self-override unless we explicitly
+        // ignore that interface relationship.
+        if (interfaceCls == member.enclosingClass) continue;
+        final match = elementEnv.lookupLocalClassMember(interfaceCls, name);
+        if (match != null &&
+            !MemberHierarchyBuilder._skipMemberInternal(match)) {
+          addParent(match, interfaceCls, false);
+        } else {
+          // The interface does not contain a match so enqueue the current
+          // member to continue checking the superclasses and supertypes of the
+          // interface.
+          queue.add(_QueuedMember(member, interfaceCls, isDeclaration: false));
+        }
+      }
+    }
+
+    void addInterfaces(IndexedClass toProcess) {
+      processInterfaces(toProcess);
+
+      // Supertypes of the mixin for this class are also supertypes of this
+      // class.
+      final mixinClass = elementEnv.getEffectiveMixinClass(toProcess);
+      if (mixinClass != null) {
+        processInterfaces(mixinClass as IndexedClass);
+      }
+    }
+
+    // Check and add any interfaces for the current class.
+    addInterfaces(cls as IndexedClass);
+
+    // Check each superclass in ascending order until we find an ancestor.
+    ClassEntity? current = elementEnv.getSuperClass(cls);
+    while (current != null) {
+      final match = elementEnv.lookupLocalClassMember(current, name);
+      if (match != null && !MemberHierarchyBuilder._skipMemberInternal(match)) {
+        addParent(match, current, true);
+        break;
+      }
+
+      // If this superclass does not declare a relevant member then check its
+      // interfaces too since a supertype can contain an ancestor.
+      addInterfaces(current as IndexedClass);
+      current = elementEnv.getSuperClass(current);
+    }
+  }
+
+  void _processMember(
+      MemberEntity member,
+      Map<Selector, Map<ClassEntity, MemberEntity?>> roots,
+      void Function(MemberEntity parent, MemberEntity override) join,
+      Queue<_QueuedMember> queue) {
+    if (_skipMemberInternal(member)) return;
+    final cls = member.enclosingClass!;
+    final mixinUses = closedWorld.mixinUsesOf(cls);
+    // Process each selector matching member separately.
+    for (final selector in _selectorsForMember(member)) {
+      for (final mixinUse in mixinUses) {
+        queue.add(_QueuedMember(member, mixinUse, isDeclaration: true));
+      }
+      queue.add(_QueuedMember(member, cls, isDeclaration: true));
+      final selectorRoots = roots[selector] ??= {};
+      while (queue.isNotEmpty) {
+        _processNext(queue, selector, selectorRoots, join);
+      }
+    }
+  }
+
+  void init(void Function(MemberEntity parent, MemberEntity override) join) {
+    final liveMembers = closedWorld.liveInstanceMembers
+        .followedBy(closedWorld.liveAbstractInstanceMembers);
+
+    // Track root declarations for each selector encountered. Each inner map
+    // tracks declarations of the selector. A null member indicates we found
+    // an override for that declaration.
+    final Map<Selector, Map<ClassEntity, MemberEntity?>> roots = {};
+    final queue = Queue<_QueuedMember>();
+    for (final member in liveMembers) {
+      assert(queue.isEmpty);
+      _processMember(member, roots, join, queue);
+    }
+
+    // Finalize roots for each selector. Deduplicate remaining root declarations
+    // and store them for later.
+    roots.forEach((selector, selectorRoots) {
+      final validRoots = selectorRoots.values.whereType<MemberEntity>().toSet();
+      if (validRoots.isNotEmpty) _selectorRoots[selector] = validRoots;
+    });
+  }
+
+  // TODO(natebiggs): Clean up debug code below.
+  void debugCall(String selectorName, String className,
+      {bool nullReceiver = false,
+      bool nullValue = false,
+      bool subClass = false,
+      bool subType = false,
+      bool setter = false,
+      CallStructure? call}) {
+    final allMembers = closedWorld.liveInstanceMembers
+        .followedBy(closedWorld.liveAbstractInstanceMembers);
+    final cls = allMembers
+        .firstWhere((e) => e.enclosingClass!.name == className)
+        .enclosingClass!;
+    final domain = closedWorld.abstractValueDomain;
+    final receiver = nullValue
+        ? domain.nullType
+        : (nullReceiver
+            ? null
+            : (subClass
+                ? domain.createNonNullSubclass(cls)
+                : (subType
+                    ? domain.createNonNullSubtype(cls)
+                    : domain.createNullableExact(cls))));
+    final name = Name(selectorName, null);
+    final selector = call != null
+        ? Selector.call(name, call)
+        : (setter ? Selector.setter(name) : Selector.getter(name));
+
+    print('Receiver: $receiver, Selector: $selector');
+    print('Locate members: ${closedWorld.locateMembers(selector, receiver)}');
+    print('Targets for call: ${rootsForCall(receiver, selector).toList()}');
+  }
+
+  void dumpOverrides([String? memberName]) {
+    print(_overrides.entries
+        .where((e) => memberName == null || e.key.name == memberName)
+        .map((e) => '${e.key}\n  ${e.value.join('\n  ')}')
+        .join('\n'));
+  }
+
+  void dumpRoots([String? selectorName]) {
+    (_selectorRoots.entries
+            .where((e) => selectorName == null || e.key.name == selectorName)
+            .map((e) {
+      final members = e.value.map((m) => m).toList();
+      members.sort((a, b) => a.toString().compareTo(b.toString()));
+      return MapEntry(e.key, members.join(', '));
+    }).toList()
+          ..sort((a, b) {
+            final keyComp = a.key.toString().compareTo(b.key.toString());
+            return keyComp != 0 ? keyComp : a.value.compareTo(b.value);
+          }))
+        .forEach((e) => print('${e.key}: ${e.value}'));
+  }
+
+  void dumpCache({String? selectorName, String? receiverString}) {
+    _callCache.entries
+        .where((e) =>
+            (selectorName == null || e.key.name == selectorName) &&
+            (receiverString == null ||
+                e.key.receiver.toString().contains(receiverString)))
+        .forEach((e) => print('${e.key}: ${e.value.join(', ')}'));
+  }
+}
diff --git a/pkg/compiler/lib/src/universe/selector.dart b/pkg/compiler/lib/src/universe/selector.dart
index b4f6902..941360f 100644
--- a/pkg/compiler/lib/src/universe/selector.dart
+++ b/pkg/compiler/lib/src/universe/selector.dart
@@ -244,7 +244,6 @@
   }
 
   bool appliesStructural(MemberEntity element) {
-    assert(name == element.name);
     if (element.isSetter) return isSetter;
     if (element.isGetter) return isGetter || isCall;
     if (element is FieldEntity) {