Add test for using applymapping with devirtualized interfaces
The MethodNameMinifier will reserve names of interfaces that may have
been merged in an optimized library - and not pick names from the
strategy. This can result in method not found exception at runtime but
also compile time errors because of name-clashes.
This CL introduces tests to highlight the problem.
Bug: 126503704
Bug: 123092153
Bug: 121305642
Change-Id: I9ec3faa0dcbe82acd0988adaaf7903f602a546f4
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2433bbb..511f328 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -82,6 +82,13 @@
return self();
}
+ public T addKeepClassAndMembersRulesWithAllowObfuscation(Class<?>... classes) {
+ for (Class<?> clazz : classes) {
+ addKeepRules("-keep,allowobfuscation class " + clazz.getTypeName() + " { *; }");
+ }
+ return self();
+ }
+
public T addKeepClassAndDefaultConstructor(Class<?>... classes) {
for (Class<?> clazz : classes) {
addKeepRules("-keep class " + clazz.getTypeName() + " { <init>(); }");
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
new file mode 100644
index 0000000..7ef141e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterDevirtualizationTest.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2019, 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.applymapping;
+
+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.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApplyMappingAfterDevirtualizationTest extends TestBase {
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("LibClassA::foo", "LibClassB::foo", "LibClassB::bar");
+
+ public interface LibInterfaceA {
+ void foo();
+ }
+
+ public interface LibInterfaceB {
+ void foo();
+ }
+
+ // LibInterfaceA should be devirtualized into LibClassA
+ public static class LibClassA implements LibInterfaceA {
+
+ @Override
+ public void foo() {
+ System.out.println("LibClassA::foo");
+ }
+ }
+
+ // LibClassB should be devirtualized into LibInterfaceB
+ public static class LibClassB implements LibInterfaceB {
+
+ @Override
+ public void foo() {
+ System.out.println("LibClassB::foo");
+ }
+
+ public void bar() {
+ System.out.println("LibClassB::bar");
+ }
+
+ public static void main(String[] args) {
+ if (args.length > 0) {
+ new LibClassB().foo();
+ } else {
+ new LibClassB().bar();
+ }
+ }
+ }
+
+ public static class ProgramClass {
+
+ public static void main(String[] args) {
+ new LibClassA().foo();
+ LibClassB libClassB = new LibClassB();
+ libClassB.foo();
+ libClassB.bar();
+ }
+ }
+
+ private static final Class<?>[] LIBRARY_CLASSES = {
+ LibInterfaceA.class, LibInterfaceB.class, LibClassA.class, LibClassB.class
+ };
+
+ private static final Class<?>[] PROGRAM_CLASSES = {ProgramClass.class};
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public ApplyMappingAfterDevirtualizationTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void runOnJvm() throws Throwable {
+ Assume.assumeTrue(backend == Backend.CF);
+ testForJvm()
+ .addProgramClasses(LIBRARY_CLASSES)
+ .addProgramClasses(PROGRAM_CLASSES)
+ .run(ProgramClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Ignore("b/126503704")
+ @Test
+ public void devirtualizingNoRenamingOfOverridenNotKeptInterfaceMethods() throws Exception {
+ R8TestCompileResult libraryResult =
+ testForR8(backend)
+ .addProgramClasses(LIBRARY_CLASSES)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class)
+ .addKeepMainRule(LibClassB.class)
+ .addOptionsModification(options -> options.enableInlining = false)
+ .compile();
+
+ CodeInspector inspector = libraryResult.inspector();
+ assertThat(inspector.clazz(LibClassA.class), isPresent());
+ assertThat(inspector.clazz(LibClassB.class), isPresent());
+
+ // LibInterfaceX should have been moved into LibClassX.
+ assertThat(inspector.clazz(LibInterfaceA.class), not(isPresent()));
+ assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
+
+ testForR8(backend)
+ .noTreeShaking()
+ .noMinification()
+ .addProgramClasses(PROGRAM_CLASSES)
+ .addApplyMapping(libraryResult.getProguardMap())
+ .addLibraryClasses(LIBRARY_CLASSES)
+ .compile()
+ .addRunClasspathFiles(libraryResult.writeToZip())
+ .run(ProgramClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Ignore("b/126503704")
+ @Test
+ public void devirtualizingNoRenamingOfOverridenKeptInterfaceMethods() throws Exception {
+ R8TestCompileResult libraryResult =
+ testForR8(backend)
+ .addProgramClasses(LIBRARY_CLASSES)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(LibClassA.class, LibInterfaceA.class)
+ .addKeepMainRule(LibClassB.class)
+ .addOptionsModification(options -> options.enableInlining = false)
+ .compile();
+
+ CodeInspector inspector = libraryResult.inspector();
+ assertThat(inspector.clazz(LibClassA.class), isPresent());
+ assertThat(inspector.clazz(LibClassB.class), isPresent());
+
+ // LibInterfaceA is now kept.
+ assertThat(inspector.clazz(LibInterfaceA.class), isPresent());
+ assertThat(inspector.clazz(LibInterfaceB.class), not(isPresent()));
+
+ testForR8(backend)
+ .noTreeShaking()
+ .noMinification()
+ .addProgramClasses(PROGRAM_CLASSES)
+ .addApplyMapping(libraryResult.getProguardMap())
+ .addLibraryClasses(LIBRARY_CLASSES)
+ .compile()
+ .addRunClasspathFiles(libraryResult.writeToZip())
+ .run(ProgramClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+}