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 {