Desugared lib: use resolution

Bug:145787526
Change-Id: Ie43bcaeeb6dbeea54680fb8fa7747cbfed92d416
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index cf3b100..393c334 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -363,7 +363,7 @@
       return result;
     }
     // Finally Step 3:
-    return resolveMethodStep3(clazz, method, clazz);
+    return resolveMethodStep3(clazz, method);
   }
 
   /**
@@ -396,15 +396,29 @@
   }
 
   /**
+   * Helper method used for emulated interface resolution (not in JVM specifications). The result
+   * may be abstract.
+   */
+  public ResolutionResult resolveMaximallySpecificMethods(DexClass clazz, DexMethod method) {
+    assert !clazz.type.isArrayType();
+    if (clazz.isInterface()) {
+      // Look for exact method on interface.
+      DexEncodedMethod result = clazz.lookupMethod(method);
+      if (result != null) {
+        return new SingleResolutionResult(clazz, clazz, result);
+      }
+    }
+    return resolveMethodStep3(clazz, method);
+  }
+
+  /**
    * Implements step 3 of <a
    * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
    * 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share one
    * implementation.
    */
-  public ResolutionResult resolveMethodStep3(
-      DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
-    MaximallySpecificMethodsBuilder builder =
-        new MaximallySpecificMethodsBuilder(initialResolutionHolder);
+  private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
+    MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder(clazz);
     resolveMethodStep3Helper(clazz, method, builder);
     return builder.resolve();
   }
@@ -494,7 +508,7 @@
     }
     // Step 3: Look for maximally-specific superinterface methods or any interface definition.
     //         This is the same for classes and interfaces.
-    return resolveMethodStep3(definition, desc, definition);
+    return resolveMethodStep3(definition, desc);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index e559247..3168dde 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -324,7 +324,7 @@
       target =
           appView
               .appInfo()
-              .resolveMethodStep3(libraryHolder, method, libraryHolder)
+              .resolveMaximallySpecificMethods(libraryHolder, method)
               .getSingleTarget();
       if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
         targetHolder = appView.definitionFor(target.method.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 5d01e19..dbf128f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -104,8 +105,6 @@
   private final Map<DexType, DexType> emulatedInterfaces = new IdentityHashMap<>();
   // The emulatedMethod set is there to avoid doing the emulated look-up too often.
   private final Set<DexString> emulatedMethods = Sets.newIdentityHashSet();
-  private ConcurrentHashMap<DexMethod, DexType> nearestEmulatedInterfaceCache =
-      new ConcurrentHashMap<>();
 
   // All forwarding methods generated during desugaring. We don't synchronize access
   // to this collection since it is only filled in ClassProcessor running synchronously.
@@ -273,7 +272,7 @@
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
           } else {
-            DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+            DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
             if (dexType != null) {
               // That invoke super may not resolve since the super method may not be present
               // since it's in the emulated interface. We need to force resolution. If it resolves
@@ -374,7 +373,7 @@
         if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
           InvokeMethod invokeMethod = instruction.asInvokeMethod();
           DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-          DexType dexType = nearestEmulatedInterfaceOrNull(invokedMethod);
+          DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
           if (dexType != null) {
             rewriteCurrentInstructionToEmulatedInterfaceCall(
                 dexType, invokedMethod, invokeMethod, instructions);
@@ -384,7 +383,7 @@
     }
   }
 
-  private DexType nearestEmulatedInterfaceOrNull(DexMethod invokedMethod) {
+  private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
     // Here we try to avoid doing the expensive look-up on all invokes.
     if (!emulatedMethods.contains(invokedMethod.name)) {
       return null;
@@ -394,20 +393,30 @@
     if (dexClass == null) {
       return null;
     }
-    // TODO(b/120884788): Make sure program class are looked up before library class for
-    // CoreLib compilation or look again into all desugared library emulation.
-    // Outside of core libraries, only library classes are rewritten. In core libraries,
-    // some classes are present both as program and library class, and definitionFor
-    // answers the program class so this is not true.
+    // TODO(b/120884788): Make sure program class are looked up before library class.
+    // Since program classes are desugared, no need to rewrite invokes which can target only
+    // program types.
     if (!appView.options().isDesugaredLibraryCompilation() && !dexClass.isLibraryClass()) {
       return null;
     }
-    // We always rewrite interfaces, but classes are rewritten only if they are not already
-    // desugared (CoreLibrary classes efficient implementation).
-    if (!dexClass.isInterface() && isInDesugaredLibrary(dexClass)) {
+    // Since desugared library classes are desugared, no need to rewrite invokes which can target
+    // only such classes program types.
+    if (appView.rewritePrefix.hasRewrittenType(dexClass.type)) {
       return null;
     }
-    return nearestEmulatedInterfaceImplementingWithCache(invokedMethod);
+    ResolutionResult resolutionResult =
+        appView.appInfo().resolveMaximallySpecificMethods(dexClass, invokedMethod);
+    if (!resolutionResult.hasSingleTarget()) {
+      // At this point we are in a library class. Failures can happen with NoSuchMethod if a
+      // library class implement a method with same signature but not related to emulated
+      // interfaces.
+      return null;
+    }
+    DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+    if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.method.holder)) {
+      return singleTarget.method.holder;
+    }
+    return null;
   }
 
   private void rewriteCurrentInstructionToEmulatedInterfaceCall(
@@ -426,133 +435,7 @@
     }
   }
 
-  private DexType nearestEmulatedInterfaceImplementingWithCache(DexMethod method) {
-    DexType sentinel = DexItemFactory.nullValueType;
-    if (nearestEmulatedInterfaceCache.containsKey(method)) {
-      DexType dexType = nearestEmulatedInterfaceCache.get(method);
-      if (dexType == sentinel) {
-        return null;
-      }
-      return dexType;
-    } else {
-      DexType value = nearestEmulatedInterfaceImplementing(method);
-      DexType putValue = value == null ? sentinel : value;
-      nearestEmulatedInterfaceCache.put(method, putValue);
-      return value;
-    }
-  }
-
-  private DexType nearestEmulatedInterfaceImplementing(DexMethod method) {
-    // Find the nearest emulated interface implementing method in a non abstract way.
-    // Answers null if none.
-    if (!method.holder.isClassType()) {
-      return null;
-    }
-    // 1. Direct match against the interface for invokeInterface.
-    if (isMatchingEmulatedInterface(method.holder, method)) {
-      return method.holder;
-    }
-    List<DexType> foundInterfaces = new ArrayList<>();
-    Set<DexType> foundEmulatedInterfaces = Sets.newIdentityHashSet();
-    // 2. Walk superclass hierarchy to find implemented interfaces, pick the minimal of them.
-    DexType current = method.holder;
-    DexClass currentClass = appView.definitionFor(current);
-    while (currentClass != null) {
-      for (DexType itf : currentClass.interfaces.values) {
-        if (isMatchingEmulatedInterface(itf, method)) {
-          foundEmulatedInterfaces.add(itf);
-        } else if (foundEmulatedInterfaces.isEmpty()) {
-          foundInterfaces.add(itf);
-        }
-      }
-      current = currentClass.superType;
-      currentClass = current == null ? null : appView.definitionFor(current);
-    }
-    if (!foundEmulatedInterfaces.isEmpty()) {
-      return minimalInterfaceOf(foundEmulatedInterfaces);
-    }
-    // 3. Walk the interfaces hierachies to find implemented interfaces, pick the minimal of them.
-    LinkedList<DexType> workList = new LinkedList<>(foundInterfaces);
-    while (!workList.isEmpty()) {
-      currentClass = appView.definitionFor(workList.removeFirst());
-      if (currentClass != null) {
-        for (DexType itf : currentClass.interfaces.values) {
-          if (isMatchingEmulatedInterface(itf, method)) {
-            foundEmulatedInterfaces.add(itf);
-          } else if (!foundInterfaces.contains(itf)) {
-            foundInterfaces.add(itf);
-            workList.add(itf);
-          }
-        }
-      }
-    }
-    if (!foundEmulatedInterfaces.isEmpty()) {
-      return minimalInterfaceOf(foundEmulatedInterfaces);
-    }
-    return null;
-  }
-
-  private boolean isMatchingEmulatedInterface(DexType itf, DexMethod method) {
-    DexClass dexClass = appView.definitionFor(itf);
-    DexEncodedMethod encodedMethod = dexClass == null ? null : dexClass.lookupMethod(method);
-    return emulatedInterfaces.containsKey(itf)
-        && encodedMethod != null
-        && !encodedMethod.isAbstract();
-  }
-
-  private DexType minimalInterfaceOf(Set<DexType> interfaces) {
-    assert interfaces.size() > 0;
-    if (interfaces.size() == 1) {
-      return interfaces.iterator().next();
-    }
-    // We may have two classes which appears unrelated due to a missing interface in the list,
-    // i.e., A implements B implements C, but B is not implementing the method.
-    // We look up interface hierarchy here for all interfaces to determine interfaces with children.
-    // The unique interface without children is returned (nearest interface).
-    final ArrayList<DexType> hasChildren = new ArrayList<>();
-    for (DexType anInterface : interfaces) {
-      LinkedList<DexType> workList = new LinkedList<>();
-      workList.add(anInterface);
-      while (!workList.isEmpty()) {
-        DexType itf = workList.removeFirst();
-        DexClass itfClass = appView.definitionFor(itf);
-        if (itfClass == null) {
-          continue;
-        }
-        for (DexType superItf : itfClass.interfaces.values) {
-          if (interfaces.contains(superItf)) {
-            hasChildren.add(superItf);
-          } else {
-            workList.add(superItf);
-          }
-        }
-      }
-    }
-    DexType result = null;
-    for (DexType anInterface : interfaces) {
-      if (!hasChildren.contains(anInterface)) {
-        if (result != null) {
-          throw new CompilationError(
-              "Multiple emulated interfaces, non related to each other, "
-                  + "implementing the same default method ("
-                  + anInterface
-                  + ","
-                  + result
-                  + ")");
-        }
-        result = anInterface;
-      }
-    }
-    if (result == null) {
-      throw new CompilationError(
-          "All emulated interfaces "
-              + Arrays.toString(interfaces.toArray())
-              + " inherit from each other.");
-    }
-    return result;
-  }
-
-  boolean isNonDesugaredLibraryClass(DexClass clazz) {
+  private boolean isNonDesugaredLibraryClass(DexClass clazz) {
     return clazz.isLibraryClass() && !isInDesugaredLibrary(clazz);
   }