Additional negative tests for invoke interface targets
Bug: 148271337
Change-Id: If4f225e337ff1a90527ef32a5fd2d3e99047b2d0
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index c565c4e..cccaa07 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -896,9 +896,12 @@
return this;
}
- /**
- * Add Java-bytecode program data.
- */
+ /** Add Java-bytecode program data. */
+ public Builder addClassProgramData(byte[]... data) {
+ return addClassProgramData(Arrays.asList(data));
+ }
+
+ /** Add Java-bytecode program data. */
public Builder addClassProgramData(Collection<byte[]> data) {
for (byte[] datum : data) {
addClassProgramData(datum, Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
new file mode 100644
index 0000000..a81b871
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceClInitTest.java
@@ -0,0 +1,155 @@
+// 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.resolution.interfacetargets;
+
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceClInitTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ static {
+ }
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeInterfaceClInitTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.useRuntimeAsNoneRuntime());
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(A.class, B.class)
+ .addClassProgramData(transformI(), transformMain())
+ .build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "<clinit>", appInfo.dexItemFactory());
+ Assert.assertThrows(
+ AssertionError.class,
+ () -> {
+ appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo);
+ });
+ }
+
+ private Matcher<String> getExpected() {
+ if (parameters.getRuntime().isCf()) {
+ Matcher<String> expected = containsString("java.lang.VerifyError");
+ // JDK 9 and 11 output VerifyError or ClassFormatError non-deterministically.
+ if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) {
+ expected = CoreMatchers.anyOf(expected, containsString("java.lang.ClassFormatError"));
+ }
+ return expected;
+ }
+ assert parameters.getRuntime().isDex();
+ DexRuntime dexRuntime = parameters.getRuntime().asDex();
+ if (dexRuntime.getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_TARGET)) {
+ return containsString("NoSuchMethodError");
+ }
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
+ return containsString("java.lang.VerifyError");
+ }
+ return containsString("java.lang.VerifyError");
+ }
+
+ @Test
+ public void testRuntimeClInit()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, B.class)
+ .addProgramClassFileData(transformMain(), transformI())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(getExpected());
+ }
+
+ @Test
+ public void testR8ClInit() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class)
+ .addProgramClassFileData(transformMain(), transformI())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(getExpected());
+ }
+
+ private byte[] transformI() throws IOException {
+ return transformer(I.class)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access,
+ String name,
+ String descriptor,
+ String signature,
+ String[] exceptions) {
+ return super.visitMethod(access, "<clinit>", descriptor, signature, exceptions);
+ }
+ })
+ .transform();
+ }
+
+ private byte[] transformMain() throws IOException {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "callClInit",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.apply(opcode, owner, "<clinit>", descriptor, isInterface))
+ .transform();
+ }
+
+ public interface I {
+
+ default void foo() { // <-- will be rewritten to <clinit>
+ System.out.println("I.foo");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B implements I {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callClInit(args.length == 0 ? new A() : new B());
+ }
+
+ private static void callClInit(I i) {
+ i.foo(); // <-- will be i.<clinit>()
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
new file mode 100644
index 0000000..ede5785
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -0,0 +1,131 @@
+// 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.resolution.interfacetargets;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvokeInterfaceWithStaticTargetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InvokeInterfaceWithStaticTargetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testResolution() throws Exception {
+ assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ AppInfoWithLiveness appInfo =
+ computeAppViewWithLiveness(
+ buildClasses(A.class, I.class).addClassProgramData(transformMain()).build(),
+ Main.class)
+ .appInfo();
+ DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
+ Assert.assertThrows(
+ AssertionError.class,
+ () -> appInfo.resolveMethod(method.holder, method).lookupInterfaceTargets(appInfo));
+ }
+
+ @Test
+ public void testRuntimeClInit()
+ throws IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(A.class, I.class)
+ .addProgramClassFileData(transformMain())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(getExpected()));
+ }
+
+ @Test
+ public void testR8ClInit() throws IOException, CompilationFailedException, ExecutionException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, I.class)
+ .addProgramClassFileData(transformMain())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatches(containsString(getExpected()));
+ }
+
+ private String getExpected() {
+ return parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+ ? "IncompatibleClassChangeError"
+ : "NoSuchMethodError";
+ }
+
+ private byte[] transformMain() throws IOException {
+ return transformer(Main.class)
+ .transformMethodInsnInMethod(
+ "callFooBar",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("notify")) {
+ continuation.apply(
+ INVOKEINTERFACE,
+ DescriptorUtils.getBinaryNameFromJavaType(I.class.getTypeName()),
+ "bar",
+ descriptor,
+ true);
+ } else {
+ continuation.apply(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public interface I {
+
+ void foo();
+
+ static void bar() {
+ System.out.println("I.bar");
+ }
+ }
+
+ public static class A implements I {
+
+ @Override
+ public void foo() {
+ System.out.println("A.foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callFooBar(args.length == 0 ? () -> System.out.println("Lambda.foo") : new A());
+ }
+
+ public static void callFooBar(I i) {
+ i.foo();
+ i.notify(); // <-- will be i.bar()
+ }
+ }
+}