Additional tests for vertical merging with member rebinding to library
Change-Id: I49f163bbb70f8c2819657ccc8f20c1f96697b235
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
index 90ec4c3..4701f68 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperCallInStaticTest.java
@@ -61,10 +61,7 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines(EXPECTED)
- .inspect(
- inspector -> {
- assertThat(inspector.clazz(A.class), not(isPresent()));
- });
+ .inspect(inspector -> assertThat(inspector.clazz(A.class), not(isPresent())));
}
private byte[] getAWithRewrittenInvokeSpecialToBase() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperToLibraryTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperToLibraryTest.java
new file mode 100644
index 0000000..4c83705
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerSuperToLibraryTest.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2022, 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.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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 VerticalClassMergerSuperToLibraryTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"B::foo", "Lib::foo"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public VerticalClassMergerSuperToLibraryTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(B.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .addLibraryClasses(LibParent.class)
+ .addLibraryClassFileData(
+ transformer(LibWithFoo.class).setClassDescriptor(descriptor(Lib.class)).transform())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(Lib.class, LibParent.class)
+ .addProgramClasses(B.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addBootClasspathFiles(
+ buildOnDexRuntime(
+ parameters,
+ transformer(LibParent.class).transform(),
+ transformer(LibWithFoo.class)
+ .setClassDescriptor(descriptor(Lib.class))
+ .transform()))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(Lib.class, LibParent.class)
+ .addProgramClasses(B.class, Main.class)
+ .addProgramClassFileData(getAWithRewrittenInvokeSpecialToBase())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .compile()
+ .addBootClasspathFiles(
+ buildOnDexRuntime(
+ parameters,
+ transformer(LibParent.class).transform(),
+ transformer(LibWithFoo.class)
+ .setClassDescriptor(descriptor(Lib.class))
+ .transform()))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED)
+ .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresent()));
+ }
+
+ private byte[] getAWithRewrittenInvokeSpecialToBase() throws Exception {
+ return transformer(A.class)
+ .transformMethodInsnInMethod(
+ "callSuper",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (name.equals("foo")) {
+ continuation.visitMethodInsn(
+ INVOKESPECIAL,
+ DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+ name,
+ descriptor,
+ false);
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ public static class LibParent {
+
+ public void foo() {
+ System.out.println("LibParent::foo");
+ }
+ }
+
+ public static class Lib extends LibParent {}
+
+ /* Will be Lib when passed in at boot classpath */
+ public static class LibWithFoo extends LibParent {
+
+ @Override
+ public void foo() {
+ System.out.println("Lib::foo");
+ }
+ }
+
+ public static class A extends Lib {
+
+ @NeverInline
+ public static void callSuper(A a) {
+ a.foo(); // Will be rewritten from invoke-virtual to invoke-special A.foo();
+ }
+ }
+
+ @NeverClassInline
+ public static class B extends A {
+
+ @Override
+ public void foo() {
+ System.out.println("B::foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.foo();
+ A.callSuper(b);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerVirtualToLibraryTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerVirtualToLibraryTest.java
new file mode 100644
index 0000000..b70b14a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerVirtualToLibraryTest.java
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, 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.classmerging.vertical;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
+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 VerticalClassMergerVirtualToLibraryTest extends TestBase {
+
+ private static final String[] EXPECTED = new String[] {"B::foo", "Lib::bar"};
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public VerticalClassMergerVirtualToLibraryTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(B.class, Main.class, A.class)
+ .addLibraryClassFileData(classWithoutBarMethod(LibParent.class))
+ .addLibraryClasses(Lib.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private byte[] classWithoutBarMethod(Class<?> clazz) throws Exception {
+ return transformer(clazz).removeMethodsWithName("bar").transform();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(LibParent.class)
+ .addLibraryClassFileData(classWithoutBarMethod(Lib.class))
+ .addProgramClasses(A.class, B.class, Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addBootClasspathFiles(
+ buildOnDexRuntime(
+ parameters,
+ classWithoutBarMethod(LibParent.class),
+ transformer(Lib.class).transform()))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(LibParent.class)
+ .addLibraryClassFileData(classWithoutBarMethod(Lib.class))
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .addVerticallyMergedClassesInspector(
+ VerticallyMergedClassesInspector::assertNoClassesMerged)
+ .compile()
+ .addBootClasspathFiles(
+ buildOnDexRuntime(
+ parameters,
+ classWithoutBarMethod(LibParent.class),
+ transformer(Lib.class).transform()))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED)
+ .inspect(inspector -> assertThat(inspector.clazz(A.class), isPresent()));
+ }
+
+ public static class LibParent {
+
+ /** Will be gone on older devices */
+ public void bar() {
+ System.out.println("LibParent::bar");
+ }
+ }
+
+ public static class Lib extends LibParent {
+
+ /** Will be present on older devices */
+ @Override
+ public void bar() {
+ System.out.println("Lib::bar");
+ }
+ }
+
+ public static class A extends Lib {
+
+ @NeverInline
+ public static void callSuper(A a) {
+ a.bar();
+ }
+ }
+
+ @NeverClassInline
+ public static class B extends A {
+
+ public void foo() {
+ System.out.println("B::foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.foo();
+ A.callSuper(b);
+ }
+ }
+}