Add InterfaceRenamingTest
Bug: 76461545
Change-Id: Ic0ee79a586b1aa3f2697b612a6324d64b2f12ed9
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java
new file mode 100644
index 0000000..d42800a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, 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.naming;
+
+public class InterfaceRenamingTest {
+
+ public static final Class[] CLASSES = {
+ InterfaceRenamingTest.class,
+ InterfaceA.class,
+ InterfaceB.class,
+ ImplementationA1.class,
+ ImplementationB1.class,
+ ImplementationA2.class,
+ ImplementationB2.class,
+ };
+
+ // Since the names and parameter lists of the two methods in the two interfaces are equal,
+ // non-aggressive minification gives the methods the same minified name.
+ // However, the return types are different, so looking up the renamed name by prototype
+ // only gives a result for one of the two interfaces.
+ interface InterfaceA {
+ Boolean interfaceMethod();
+ }
+
+ interface InterfaceB {
+ Integer interfaceMethod();
+ }
+
+ // Two implementations of each interface are required
+ // to avoid the Devirtualizer hiding the buggy behavior.
+ static class ImplementationA1 implements InterfaceA {
+ @Override
+ public Boolean interfaceMethod() {
+ System.out.println("interfaceMethod1");
+ return true;
+ }
+ }
+
+ static class ImplementationA2 implements InterfaceA {
+ @Override
+ public Boolean interfaceMethod() {
+ System.out.println("interfaceMethod1 dummy");
+ return false;
+ }
+ }
+
+ static class ImplementationB1 implements InterfaceB {
+ @Override
+ public Integer interfaceMethod() {
+ System.out.println("interfaceMethod2");
+ return 10;
+ }
+ }
+
+ static class ImplementationB2 implements InterfaceB {
+ @Override
+ public Integer interfaceMethod() {
+ System.out.println("interfaceMethod2 dummy");
+ return 20;
+ }
+ }
+
+ public static void main(String[] args) {
+ invokeA(new ImplementationA1());
+ invokeB(new ImplementationB1());
+ invokeA(new ImplementationA2());
+ invokeB(new ImplementationB2());
+ }
+
+ private static void invokeA(InterfaceA instance) {
+ System.out.println("invokeA: " + instance.interfaceMethod());
+ }
+
+ private static void invokeB(InterfaceB instance) {
+ System.out.println("invokeB: " + instance.interfaceMethod());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
new file mode 100644
index 0000000..d25c061
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2018, 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.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class InterfaceRenamingTestRunner extends TestBase {
+ static final Class CLASS = InterfaceRenamingTest.class;
+ static final Class[] CLASSES = InterfaceRenamingTest.CLASSES;
+ private boolean bug = false;
+
+ @Test
+ public void testCfNoMinify() throws Exception {
+ testCf(MinifyMode.NONE);
+ }
+
+ @Test
+ public void testCfMinify() throws Exception {
+ bug = true;
+ testCf(MinifyMode.JAVA);
+ }
+
+ @Test
+ public void testCfMinifyAggressive() throws Exception {
+ testCf(MinifyMode.AGGRESSIVE);
+ }
+
+ @Test
+ public void testDexNoMinify() throws Exception {
+ testDex(MinifyMode.NONE);
+ }
+
+ @Test
+ public void testDexMinify() throws Exception {
+ bug = true;
+ testDex(MinifyMode.JAVA);
+ }
+
+ @Test
+ public void testDexMinifyAggressive() throws Exception {
+ testDex(MinifyMode.AGGRESSIVE);
+ }
+
+ private void testCf(MinifyMode minify) throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outCf = temp.getRoot().toPath().resolve("cf.zip");
+ build(new ClassFileConsumer.ArchiveConsumer(outCf), minify);
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+ if (bug) {
+ assertNotEquals(-1, runCf.stderr.indexOf("java.lang.AbstractMethodError"));
+ assertNotEquals(0, runCf.exitCode);
+ return;
+ }
+ assertEquals(runInput.toString(), runCf.toString());
+ }
+
+ private void testDex(MinifyMode minify) throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex), minify);
+ if (bug) {
+ ProcessResult runDex = ToolHelper.runArtRaw(outDex.toString(), CLASS.getCanonicalName());
+ assertNotEquals(-1, runDex.stderr.indexOf("java.lang.AbstractMethodError"));
+ assertNotEquals(0, runDex.exitCode);
+ return;
+ }
+ ProcessResult runDex =
+ ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ }
+
+ private void build(ProgramConsumer consumer, MinifyMode minify) throws Exception {
+ List<String> config =
+ Arrays.asList(
+ "-keep public class " + CLASS.getCanonicalName() + " {",
+ " public static void main(...);",
+ "}");
+
+ Builder builder =
+ ToolHelper.addProguardConfigurationConsumer(
+ R8Command.builder(),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
+ if (!minify.isMinify()) {
+ pgConfig.disableObfuscation();
+ }
+ })
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(consumer)
+ .addProguardConfiguration(config, Origin.unknown());
+ for (Class<?> c : CLASSES) {
+ builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+ }
+ if (consumer instanceof ClassFileConsumer) {
+ // TODO(b/75997473): Enable inlining when supported by CF backend
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ } else {
+ ToolHelper.runR8(builder.build());
+ }
+ }
+}