Add regression test showing invalid rewrite of super invoke into library
Bug: 209060415
Change-Id: I0cbd3b773cf526d7f2e0a77d0a60ef41e1542899
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index b80c482..b3f4a13 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.debug.DexDebugTestConfig;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -38,6 +39,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -256,6 +258,67 @@
}
}
+ public CR addRunClasspathClassFileData(byte[]... classes) throws Exception {
+ return addRunClasspathClassFileData(Arrays.asList(classes));
+ }
+
+ public CR addRunClasspathClassFileData(Collection<byte[]> classes) throws Exception {
+ if (getBackend() == Backend.DEX) {
+ additionalRunClassPath.add(
+ testForD8(state.getTempFolder())
+ .addProgramClassFileData(classes)
+ .setMinApi(minApiLevel)
+ .compile()
+ .writeToZip());
+ return self();
+ }
+ assert getBackend() == Backend.CF;
+ try {
+ AndroidApp.Builder appBuilder = AndroidApp.builder();
+ for (byte[] clazz : classes) {
+ appBuilder.addClassProgramData(clazz, Origin.unknown());
+ }
+ Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
+ appBuilder.build().writeToZip(path, OutputMode.ClassFile);
+ additionalRunClassPath.add(path);
+ return self();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public CR addBootClasspathClasses(Class<?>... classes) throws Exception {
+ return addBootClasspathClasses(Arrays.asList(classes));
+ }
+
+ public CR addBootClasspathClasses(List<Class<?>> classes) throws Exception {
+ if (getBackend() == Backend.DEX) {
+ additionalRunClassPath.add(
+ testForD8(state.getTempFolder())
+ .addProgramClasses(classes)
+ .setMinApi(minApiLevel)
+ .compile()
+ .writeToZip());
+ return self();
+ }
+ assert getBackend() == Backend.CF;
+ try {
+ Path path = state.getNewTempFolder().resolve("runtime-classes.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(path);
+ for (Class<?> clazz : classes) {
+ consumer.accept(
+ ByteDataView.of(ToolHelper.getClassAsBytes(clazz)),
+ DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()),
+ null);
+ }
+ consumer.finished(null);
+ additionalRunClassPath.add(path);
+ return self();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public CR addDesugaredCoreLibraryRunClassPath(
Function<AndroidApiLevel, Path> classPathSupplier, AndroidApiLevel minAPILevel) {
if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizeLibrarySuperTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizeLibrarySuperTest.java
new file mode 100644
index 0000000..cc04e23
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/DevirtualizeLibrarySuperTest.java
@@ -0,0 +1,116 @@
+// 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.ir.optimize.devirtualize;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+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.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DevirtualizeLibrarySuperTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ boolean hasNewLibraryHierarchyOnClassPath =
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M);
+ testForR8(parameters.getBackend())
+ .addLibraryClasses(Library.class, LibraryOverride.class, LibraryBoundary.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .compile()
+ .inspect(
+ inspector -> {
+ if (parameters.isCfRuntime()) {
+ return;
+ }
+ MethodSubject fooMethod = inspector.clazz(Main.class).uniqueMethodWithName("foo");
+ assertThat(fooMethod, isPresent());
+ // TODO(b/209060415): Should not have a static holder of `LibraryOverride`.
+ assertThat(
+ fooMethod,
+ invokesMethodWithHolderAndName(LibraryOverride.class.getTypeName(), "foo"));
+ })
+ .applyIf(
+ hasNewLibraryHierarchyOnClassPath,
+ b ->
+ b.addRunClasspathClasses(
+ Library.class, LibraryOverride.class, LibraryBoundary.class),
+ b ->
+ b.addRunClasspathClassFileData(
+ transformer(Library.class).transform(),
+ transformer(LibraryBoundary.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(LibraryOverride.class), descriptor(Library.class))
+ .setSuper(descriptor(Library.class))
+ .transform()))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLinesIf(hasNewLibraryHierarchyOnClassPath, "LibraryOverride::foo")
+ // TODO(b/209060415): Should not fail.
+ .applyIf(
+ !hasNewLibraryHierarchyOnClassPath,
+ result -> {
+ result.assertFailureWithErrorThatThrows(
+ (parameters.getDexRuntimeVersion().isDalvik()
+ || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0)
+ ? NoClassDefFoundError.class
+ : VerifyError.class));
+ });
+ }
+
+ public static class Library {
+
+ public void foo() {
+ System.out.println("Library::foo");
+ }
+ }
+
+ // This class will is inserted in the hierarchy from api 23.
+ public static class LibraryOverride extends Library {
+
+ @Override
+ public void foo() {
+ System.out.println("LibraryOverride::foo");
+ }
+ }
+
+ public static class LibraryBoundary extends LibraryOverride {}
+
+ public static class Main extends LibraryBoundary {
+
+ @NeverInline
+ @Override
+ public void foo() {
+ super.foo();
+ }
+
+ public static void main(String[] args) {
+ new Main().foo();
+ }
+ }
+}