Determine forwarding target using lookup virtual dispatch target.

Bug: 152163087
Change-Id: I4fd32fa47507dc0964061ff6b93b581b2c746267
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index e123de4..3de6525 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -45,6 +45,10 @@
     return false;
   }
 
+  public boolean isIncompatibleClassChangeErrorResult() {
+    return false;
+  }
+
   /** Returns non-null if isFailedResolution() is true, otherwise null. */
   public FailedResolutionResult asFailedResolution() {
     return null;
@@ -101,7 +105,7 @@
       InstantiatedObject instance, AppInfoWithClassHierarchy appInfo);
 
   public abstract DexClassAndMethod lookupVirtualDispatchTarget(
-      DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
+      DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
   public abstract LookupTarget lookupVirtualDispatchTarget(
       LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
@@ -516,7 +520,7 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return lookupVirtualDispatchTarget(dynamicInstance, appInfo, initialResolutionHolder.type);
     }
 
@@ -542,9 +546,7 @@
     }
 
     private DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance,
-        AppInfoWithClassHierarchy appInfo,
-        DexType resolutionHolder) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo, DexType resolutionHolder) {
       assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
           : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
@@ -584,7 +586,7 @@
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
     }
 
@@ -702,7 +704,7 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
@@ -808,6 +810,11 @@
           ? INSTANCE
           : new IncompatibleClassResult(methodsCausingError);
     }
+
+    @Override
+    public boolean isIncompatibleClassChangeErrorResult() {
+      return true;
+    }
   }
 
   public static class NoSuchMethodResult extends FailedResolutionResult {
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 ff5dd2f..82c7c6a 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -18,9 +19,9 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -74,14 +75,6 @@
       return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
     }
 
-    MethodSignatures merge(List<MethodSignatures> others) {
-      MethodSignatures merged = this;
-      for (MethodSignatures other : others) {
-        merged = merged.merge(others);
-      }
-      return merged;
-    }
-
     boolean isEmpty() {
       return signatures.isEmpty();
     }
@@ -151,7 +144,7 @@
     }
   }
 
-  // Specialized context to disable reporting when traversing the library strucure.
+  // Specialized context to disable reporting when traversing the library structure.
   private static class LibraryReportingContext extends ReportingContext {
 
     static final LibraryReportingContext LIBRARY_CONTEXT = new LibraryReportingContext();
@@ -281,22 +274,36 @@
     return ClassInfo.create(superInfo, additionalForwards.build());
   }
 
-  // Resolves a method signature from the point of 'clazz', if it must target a default method
+  // Looks up a method signature from the point of 'clazz', if it can dispatch to a default method
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, BiConsumer<DexClass, DexEncodedMethod> addForward) {
-    ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
-    // If resolution fails, install a method throwing IncompatibleClassChangeError.
-    if (resolution.isFailedResolution()) {
-      if (resolution instanceof IncompatibleClassResult) {
+    // Resolve the default method at base type as the symbolic holder at call sites is not known.
+    // The dispatch target is then looked up from the possible "instance" class.
+    // Doing so can cause an invalid invoke to become valid (at runtime resolution at a subtype
+    // might have failed which is hidden by the insertion of the forward method). However, not doing
+    // so could cause valid dispatches to become invalid by resolving to private overrides.
+    DexClassAndMethod virtualDispatchTarget =
+        appView
+            .appInfo()
+            .resolveMethodOnInterface(method.holder, method)
+            .lookupVirtualDispatchTarget(clazz, appView.appInfo());
+    if (virtualDispatchTarget == null) {
+      // If no target is found due to multiple default method targets, preserve ICCE behavior.
+      ResolutionResult resolutionFromSubclass = appView.appInfo().resolveMethod(clazz, method);
+      if (resolutionFromSubclass.isIncompatibleClassChangeErrorResult()) {
         addICCEThrowingMethod(method, clazz);
+        return;
       }
+      assert resolutionFromSubclass.isFailedResolution()
+          || resolutionFromSubclass.getSingleTarget().isPrivateMethod();
       return;
     }
-    DexEncodedMethod target = resolution.getSingleTarget();
-    DexClass targetHolder = appView.definitionFor(target.holder());
+
+    DexEncodedMethod target = virtualDispatchTarget.getMethod();
+    DexClass targetHolder = virtualDispatchTarget.getHolder();
     // Don-t forward if the target is explicitly marked as 'dont-rewrite'
-    if (targetHolder == null || dontRewrite(targetHolder, target)) {
+    if (dontRewrite(targetHolder, target)) {
       return;
     }
 
@@ -375,6 +382,16 @@
     if (!clazz.isProgramClass()) {
       return;
     }
+
+    DexEncodedMethod methodOnSelf = clazz.lookupMethod(target.method);
+    if (methodOnSelf != null) {
+      throw new CompilationError(
+          "Attempt to add forwarding method that conflicts with existing method.",
+          null,
+          clazz.getOrigin(),
+          new MethodPosition(methodOnSelf.method));
+    }
+
     DexMethod method = target.method;
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringImpossibleTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringImpossibleTest.java
new file mode 100644
index 0000000..88ede59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringImpossibleTest.java
@@ -0,0 +1,128 @@
+// Copyright (c) 2020, the R8 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.
+package com.android.tools.r8.desugaring.interfacemethods;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodDesugaringImpossibleTest extends TestBase {
+
+  private static final String EXPECTED = StringUtils.lines("I.m()");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultInterfaceMethodDesugaringImpossibleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private Collection<Class<?>> getProgramClasses() {
+    return ImmutableList.of(TestClass.class, I.class);
+  }
+
+  private Collection<byte[]> getProgramClassData() throws Exception {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setAccessFlags(
+                A.class.getDeclaredMethod("m"),
+                flags -> {
+                  assert flags.isPublic();
+                  flags.unsetPublic();
+                  flags.setPrivate();
+                  flags.setStatic();
+                })
+            .transform());
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(getProgramClasses())
+        .addProgramClassFileData(getProgramClassData())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    try {
+      testForD8()
+          .addProgramClasses(getProgramClasses())
+          .addProgramClassFileData(getProgramClassData())
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(this::checkDesugarError)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      assertTrue(parameters.canUseDefaultAndStaticInterfaceMethods());
+    } catch (CompilationFailedException e) {
+      assertFalse(parameters.canUseDefaultAndStaticInterfaceMethods());
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(getProgramClasses())
+          .addProgramClassFileData(getProgramClassData())
+          .addKeepAllClassesRule()
+          .setMinApi(parameters.getApiLevel())
+          .compileWithExpectedDiagnostics(this::checkDesugarError)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      assertTrue(parameters.canUseDefaultAndStaticInterfaceMethods());
+    } catch (CompilationFailedException e) {
+      assertFalse(parameters.canUseDefaultAndStaticInterfaceMethods());
+    }
+  }
+
+  private void checkDesugarError(TestDiagnosticMessages diagnostics) {
+    if (!parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      diagnostics.assertErrorMessageThatMatches(containsString("forwarding method that conflicts"));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I a = new A();
+      a.m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
+
+  static class A implements I {
+
+    public /* will be: private static */ void m() {
+      System.out.println("A.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
index bcea446..a8c5c6f 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionInvokeVirtualTest.java
@@ -106,11 +106,18 @@
         result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
         return;
       }
-      // Then up to 6.0 the the runtime just ignores privates leading to incorrectly hitting I.m
+      // Then up to 6.0 the runtime just ignores privates leading to incorrectly hitting I.m
       if (isDexOlderThanOrEqual(Version.V6_0_1)) {
         result.assertSuccessWithOutput(EXPECTED);
         return;
       }
+      if (!unexpectedArtFailure() && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
+        assert false : "Dead code until future ART behavior change. See b/152199517";
+        // Desugaring will insert a forwarding bridge which will hide the "invalid invoke" case.
+        // Thus, a future ART runtime that does not have the invalid IAE for the private override
+        // will end up calling the forward method to I.m.
+        result.assertSuccessWithOutput(EXPECTED);
+      }
       // The expected behavior is IAE since the resolved method is private.
       result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
       return;
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
index bdd7960..c649c8d 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
@@ -1,13 +1,11 @@
 package com.android.tools.r8.desugaring.interfacemethods;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -15,6 +13,8 @@
 @RunWith(Parameterized.class)
 public class DefaultInterfaceMethodDesugaringWithStaticResolutionTest extends TestBase {
 
+  private static final String EXPECTED = StringUtils.lines("I.m()");
+
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
@@ -32,43 +32,29 @@
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("I.m()");
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
-    D8TestRunResult result =
-        testForD8()
-            .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-    // TODO(b/152163087): Should always succeed with "I.m()".
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      result.assertSuccessWithOutputLines("I.m()");
-    } else {
-      result.assertFailureWithErrorThatMatches(
-          containsString(AbstractMethodError.class.getTypeName()));
-    }
+    testForD8()
+        .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testR8() throws Exception {
-    R8TestRunResult result =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
-            .addKeepAllClassesRule()
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .run(parameters.getRuntime(), TestClass.class);
-    // TODO(b/152163087): Should always succeed with "I.m()".
-    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-      result.assertSuccessWithOutputLines("I.m()");
-    } else {
-      result.assertFailureWithErrorThatMatches(
-          containsString(AbstractMethodError.class.getTypeName()));
-    }
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+        .addKeepAllClassesRule()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
   }
 
   static class TestClass {