Account for NoSuchMethodResult when having multiple definitions

Bug: b/226170842
Change-Id: Id9bc61633f57a4ff19b70fcd255f9643827c8d81
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolution.java b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
index 990f432..983c69e 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolution.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolution.java
@@ -93,15 +93,12 @@
     definitionFor(holder)
         .forEachClassResolutionResult(
             clazz -> {
-              if (clazz.isInterface()) {
-                builder.addResolutionResult(
-                    resolveMethodOnInterface(clazz, method.getProto(), method.getName()));
-              } else {
-                builder.addResolutionResult(
-                    resolveMethodOnClass(clazz, method.getProto(), method.getName()));
-              }
+              builder.addResolutionResult(
+                  clazz.isInterface()
+                      ? resolveMethodOnInterface(clazz, method.getProto(), method.getName())
+                      : resolveMethodOnClass(clazz, method.getProto(), method.getName()));
             });
-    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, holder);
   }
 
   /**
@@ -148,7 +145,7 @@
                 builder.addResolutionResult(resolveMethodOnClass(clazz, methodProto, methodName));
               }
             });
-    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, holder);
   }
 
   public MethodResolutionResult resolveMethodOnClass(
@@ -197,8 +194,8 @@
           initialResolutionHolder, clazz, result);
     }
     // Pt 3: Apply step two to direct superclass of holder.
-    MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
     if (clazz.superType != null) {
+      MethodResolutionResult.Builder builder = MethodResolutionResult.builder();
       definitionFor(clazz.superType)
           .forEachClassResolutionResult(
               superClass -> {
@@ -212,8 +209,9 @@
                     resolveMethodOnClassStep2(
                         superClass, methodProto, methodName, initialResolutionHolder));
               });
+      return builder.buildOrIfEmpty(null, clazz.superType);
     }
-    return builder.buildOrIfEmpty(null);
+    return null;
   }
 
   /**
@@ -522,7 +520,7 @@
                     resolveMethodOnInterface(definition, proto, methodName));
               }
             });
-    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, holder);
   }
 
   public MethodResolutionResult resolveMethodOnInterface(
@@ -552,7 +550,7 @@
                     resolveMethodStep3(definition, methodProto, methodName));
               }
             });
-    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE);
+    return builder.buildOrIfEmpty(ClassNotFoundResult.INSTANCE, Collections.emptySet());
   }
 
   static class MaximallySpecificMethodsBuilder {
@@ -674,7 +672,7 @@
             MethodResolutionResult.builder().allowMultipleProgramResults();
         if (nonAbstractOnComplete.isEmpty()) {
           assert !typesWithMultipleDefinitions.isEmpty();
-          builder.addResolutionResult(new NoSuchMethodResult(typesWithMultipleDefinitions));
+          builder.addResolutionResult(NoSuchMethodResult.INSTANCE);
         } else {
           nonAbstractOnComplete.forEach(
               entry ->
@@ -683,7 +681,7 @@
         nonAbstractOnIncomplete.forEach(
             entry ->
                 builder.addResolutionResult(singleResultHelper(initialResolutionHolder, entry)));
-        return builder.buildOrIfEmpty(NoSuchMethodResult.INSTANCE);
+        return builder.buildOrIfEmpty(NoSuchMethodResult.INSTANCE, typesWithMultipleDefinitions);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index a2dd885..6b6991d 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -16,11 +16,14 @@
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
@@ -68,10 +71,18 @@
     return false;
   }
 
+  public boolean isNoSuchMethodResultDueToMultipleClassDefinitions() {
+    return false;
+  }
+
   public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
     return false;
   }
 
+  public boolean internalIsInstanceOfNoSuchMethodResult() {
+    return false;
+  }
+
   public boolean isIllegalAccessErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
     return false;
   }
@@ -97,6 +108,10 @@
     return null;
   }
 
+  public NoSuchMethodResult asNoSuchMethodResult() {
+    return null;
+  }
+
   public DexClass getResolvedHolder() {
     return null;
   }
@@ -1172,9 +1187,13 @@
 
   public static class NoSuchMethodResult extends FailedResolutionResult {
 
-    static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult(null);
+    static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
 
-    public NoSuchMethodResult(Collection<DexType> typesCausingError) {
+    private NoSuchMethodResult() {
+      super(null);
+    }
+
+    protected NoSuchMethodResult(Collection<DexType> typesCausingError) {
       super(typesCausingError);
     }
 
@@ -1182,6 +1201,28 @@
     public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
       return true;
     }
+
+    @Override
+    public boolean internalIsInstanceOfNoSuchMethodResult() {
+      return true;
+    }
+
+    @Override
+    public NoSuchMethodResult asNoSuchMethodResult() {
+      return this;
+    }
+  }
+
+  public static class NoSuchMethodResultDueToMultipleClassDefinitions extends NoSuchMethodResult {
+
+    public NoSuchMethodResultDueToMultipleClassDefinitions(Collection<DexType> typesCausingError) {
+      super(typesCausingError);
+    }
+
+    @Override
+    public boolean isNoSuchMethodResultDueToMultipleClassDefinitions() {
+      return true;
+    }
   }
 
   static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
@@ -1463,7 +1504,15 @@
       return this;
     }
 
-    public MethodResolutionResult buildOrIfEmpty(MethodResolutionResult emptyResult) {
+    public MethodResolutionResult buildOrIfEmpty(
+        MethodResolutionResult emptyResult, DexType responsibleTypeForNoSuchMethodResult) {
+      return buildOrIfEmpty(
+          emptyResult, Collections.singletonList(responsibleTypeForNoSuchMethodResult));
+    }
+
+    public MethodResolutionResult buildOrIfEmpty(
+        MethodResolutionResult emptyResult,
+        Collection<DexType> responsibleTypesForNoSuchMethodResult) {
       if (possiblySingleResult == null) {
         return emptyResult;
       } else if (allResults == null) {
@@ -1473,6 +1522,7 @@
           new ArrayList<>();
       List<SingleLibraryResolutionResult> libraryResults = new ArrayList<>();
       List<FailedResolutionResult> failedResults = new ArrayList<>();
+      Set<NoSuchMethodResult> noSuchMethodResults = Sets.newIdentityHashSet();
       allResults.forEach(
           otherResult -> {
             otherResult.visitMethodResolutionResults(
@@ -1492,14 +1542,25 @@
                 },
                 ConsumerUtils.emptyConsumer(),
                 newFailedResult -> {
-                  if (!Iterables.any(
-                      failedResults,
-                      existing ->
-                          existing.isFailedResolution() == newFailedResult.isFailedResolution())) {
+                  if (newFailedResult.internalIsInstanceOfNoSuchMethodResult()) {
+                    noSuchMethodResults.add(newFailedResult.asNoSuchMethodResult());
+                  }
+                  if (!Iterables.any(failedResults, existing -> existing == newFailedResult)) {
                     failedResults.add(newFailedResult);
                   }
                 });
           });
+      // If we have seen a NoSuchMethod and also a successful result it must be because we have
+      // multiple definitions of a type. Here we compute a single NoSuchMethodResult with root types
+      // that must be preserved to still observe the NoSuchMethodError.
+      if (!noSuchMethodResults.isEmpty()) {
+        if (!libraryResults.isEmpty() || !programOrClasspathResults.isEmpty()) {
+          failedResults.add(
+              mergeNoSuchMethodErrors(noSuchMethodResults, responsibleTypesForNoSuchMethodResult));
+        } else {
+          failedResults.add(NoSuchMethodResult.INSTANCE);
+        }
+      }
       if (programOrClasspathResults.isEmpty()) {
         if (libraryResults.size() == 1 && failedResults.isEmpty()) {
           return libraryResults.get(0);
@@ -1531,5 +1592,21 @@
             programOrClasspathResults, libraryResults, failedResults);
       }
     }
+
+    private NoSuchMethodResult mergeNoSuchMethodErrors(
+        Set<NoSuchMethodResult> noSuchMethodErrors, Collection<DexType> typesCausingErrorsHere) {
+      Set<DexType> typesCausingError = SetUtils.newIdentityHashSet(typesCausingErrorsHere);
+      noSuchMethodErrors.forEach(
+          failedResolutionResult -> {
+            assert failedResolutionResult == NoSuchMethodResult.INSTANCE
+                || failedResolutionResult.isNoSuchMethodResultDueToMultipleClassDefinitions();
+            if (failedResolutionResult.typesCausingError != null) {
+              typesCausingError.addAll(failedResolutionResult.typesCausingError);
+            }
+          });
+      return typesCausingError.isEmpty()
+          ? NoSuchMethodResult.INSTANCE
+          : new NoSuchMethodResultDueToMultipleClassDefinitions(typesCausingError);
+    }
   }
 }