Handle direct targets to inaccessible interface methods.

Change-Id: I0cd24204845c5ba489f9107614b4dda0ecc3000d
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 1f737f2..c4c281d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -544,8 +544,21 @@
           getMethodOrigin(context.getReference()));
     }
 
+    MethodResolutionResult resolution =
+        appView.appInfoForDesugaring().resolveMethod(invokedMethod, invoke.isInterface());
+    if (resolution.isFailedResolution()) {
+      return computeInvokeAsThrowRewrite(invoke, null);
+    }
+
+    SingleResolutionResult singleResolution = resolution.asSingleResolution();
+    if (singleResolution == null) {
+      return DesugarDescription.nothing();
+    }
+
     DexClassAndMethod directTarget = clazz.lookupClassMethod(invokedMethod);
     if (directTarget != null) {
+      // TODO(b/199135051): Replace this by use of the resolution result.
+      assert directTarget.getDefinition() == singleResolution.getResolutionPair().getDefinition();
       return DesugarDescription.builder()
           .setDesugarRewrite(
               (freshLocalProvider,
@@ -585,6 +598,9 @@
       DexClassAndMethod virtualTarget =
           appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, invokedMethod);
       if (virtualTarget != null) {
+        // TODO(b/199135051): Replace this by use of the resolution result.
+        assert virtualTarget.getDefinition()
+            == singleResolution.getResolutionPair().getDefinition();
         return DesugarDescription.builder()
             .setDesugarRewrite(
                 (freshLocalProvider,
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java
index 04dc573..ed7b9eb 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java
@@ -6,8 +6,8 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
@@ -68,17 +68,20 @@
               } else {
                 action = inspector.companionClassFor(Action.class);
               }
-              assertThat(action, isPresent());
-              MethodSubject action$lambda$getAction$0 =
-                  action.uniqueMethodWithName("lambda$getAction$0");
-              assertThat(action$lambda$getAction$0, isPresent());
-              assertThat(
-                  action$lambda$getAction$0, CodeMatchers.invokesMethodWithName("apiLevel22"));
               ClassSubject apiCaller = inspector.clazz(ApiCaller.class);
               if (parameters.isDexRuntime()
                   && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) {
-                assertThat(apiCaller, not(isPresent()));
+                // Both Action and ApiCaller have been optimized out.
+                assertThat(action, isAbsent());
+                assertThat(apiCaller, isAbsent());
               } else {
+                assertThat(action, isPresent());
+                MethodSubject action$lambda$getAction$0 =
+                    action.uniqueMethodWithName("lambda$getAction$0");
+                assertThat(action$lambda$getAction$0, isPresent());
+                assertThat(
+                    action$lambda$getAction$0, CodeMatchers.invokesMethodWithName("apiLevel22"));
+
                 assertThat(apiCaller, isPresent());
                 MethodSubject callApi = apiCaller.uniqueMethodWithName("callApi");
                 assertThat(callApi, isPresent());
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeInterfaceToNonAccessPrivateInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeInterfaceToNonAccessPrivateInterfaceMethodTest.java
new file mode 100644
index 0000000..96e2dae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeInterfaceToNonAccessPrivateInterfaceMethodTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceToNonAccessPrivateInterfaceMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public InvokeInterfaceToNonAccessPrivateInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(getTransformedI())
+        .addProgramClassFileData(getAndCheckJ())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  private byte[] getTransformedI() throws Exception {
+    return transformer(I.class).setPrivate(I.class.getMethod("foo")).transform();
+  }
+
+  private byte[] getAndCheckJ() throws Exception {
+    return transformer(J.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              assertEquals("foo", name);
+              assertEquals(Opcodes.INVOKEINTERFACE, opcode);
+              visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(getTransformedI())
+        .addProgramClassFileData(getAndCheckJ())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  interface I {
+
+    default /* will be private */ void foo() {
+      System.out.println("Called I.foo");
+    }
+  }
+
+  interface J extends I {
+
+    default void bar() {
+      foo();
+    }
+  }
+
+  static class A implements J {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSpecialToNonAccessPrivateInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSpecialToNonAccessPrivateInterfaceMethodTest.java
new file mode 100644
index 0000000..1d77e8e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSpecialToNonAccessPrivateInterfaceMethodTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, 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.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToNonAccessPrivateInterfaceMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public InvokeSpecialToNonAccessPrivateInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(getTransformedI())
+        .addProgramClassFileData(getTransformedJ())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  private byte[] getTransformedI() throws Exception {
+    return transformer(I.class).setPrivate(I.class.getMethod("foo")).transform();
+  }
+
+  private byte[] getTransformedJ() throws Exception {
+    return transformer(J.class)
+        .transformMethodInsnInMethod(
+            "bar",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              assertEquals("foo", name);
+              visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface);
+            })
+        .transform();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class)
+        .addProgramClassFileData(getTransformedI())
+        .addProgramClassFileData(getTransformedJ())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  interface I {
+
+    default /* will be private */ void foo() {
+      System.out.println("Called I.foo");
+    }
+  }
+
+  interface J extends I {
+
+    default void bar() {
+      foo(); // will be invoke-special
+    }
+  }
+
+  static class A implements J {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java b/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
index ce969b7..ee55c90 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B161735546.java
@@ -26,7 +26,15 @@
   }
 
   @Test
-  public void test() throws Exception {
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(B161735546.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("1", "2", "3");
+  }
+
+  @Test
+  public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(B161735546.class)
         .addKeepMainRule(TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java
index 27a8639..1b31ea7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress199142666.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.io.IOException;
@@ -58,28 +57,24 @@
           containsString("invoke type does not match method type of"));
     } else {
       run.assertSuccessWithOutputLines("foochanged")
-          .inspect(inspector -> ensureThisNumberOfCalls(inspector, B.class, 2));
+          .inspect(inspector -> ensureThisNumberOfCalls(inspector, B.class));
     }
   }
 
   @Test
   public void testInliningWhenInvalidCaller() throws Exception {
-    // On pre 7 we do interface desugaring, where we insert ICCE directly if that would happen
-    // at runtime, even when there are no interfaces involved. Tracked in b/199135051
-    int assumedFooCalls = parameters.getApiLevel().isLessThan(AndroidApiLevel.N) ? 1 : 2;
-    R8TestRunResult run =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(C.class)
-            .addProgramClassFileData(getStaticAAsVirtualA())
-            .addKeepMainRule(C.class)
-            .addKeepMethodRules(VirtualA.class, "static void foo()")
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), C.class)
-            .assertSuccessWithOutputLines("foo")
-            .inspect(inspector -> ensureThisNumberOfCalls(inspector, C.class, assumedFooCalls));
+    testForR8(parameters.getBackend())
+        .addProgramClasses(C.class)
+        .addProgramClassFileData(getStaticAAsVirtualA())
+        .addKeepMainRule(C.class)
+        .addKeepMethodRules(VirtualA.class, "static void foo()")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), C.class)
+        .assertSuccessWithOutputLines("foo")
+        .inspect(inspector -> ensureThisNumberOfCalls(inspector, C.class));
   }
 
-  private void ensureThisNumberOfCalls(CodeInspector inspector, Class clazz, int fooCalls) {
+  private void ensureThisNumberOfCalls(CodeInspector inspector, Class clazz) {
     long count =
         inspector
             .clazz(clazz)
@@ -89,7 +84,7 @@
             .filter(invoke -> invoke.getMethod().name.toString().equals("foo"))
             .count();
     // TODO(b/199142666): We should not inline, so count should be 1.
-    assertEquals(count, fooCalls);
+    assertEquals(2, count);
   }
 
   static class StaticA {