Add test for incorrect memberrebinding to first library class
Bug: b/254510678
Change-Id: I316610a17b524521fbb43adb2273d069ad4eb909
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingFrontierTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingFrontierTest.java
new file mode 100644
index 0000000..2584946
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingFrontierTest.java
@@ -0,0 +1,136 @@
+// 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.memberrebinding;
+
+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.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+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 MemberRebindingFrontierTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClasses(Main.class, ProgramClass.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(Base.class, I.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .apply(this::setupRunclasspath)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, ProgramClass.class)
+ .addProgramClassFileData()
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(I.class)
+ .addLibraryClassFileData(removeFooMethod(Base.class))
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassRules(ProgramClass.class)
+ .addKeepMainRule(Main.class)
+ .compile()
+ .apply(this::setupRunclasspath)
+ .inspect(
+ inspector -> {
+ // TODO(b/254510678): We should not rebind to I.foo
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertThat(mainClass, isPresent());
+ MethodSubject foo = mainClass.mainMethod();
+ assertThat(
+ foo, CodeMatchers.invokesMethodWithHolderAndName(typeName(I.class), "foo"));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, true));
+ }
+
+ private byte[] removeFooMethod(Class<?> clazz) throws Exception {
+ return transformer(clazz).removeMethodsWithName("foo").transform();
+ }
+
+ private void setupRunclasspath(TestCompileResult<?, ?> compileResult) {
+ compileResult
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result ->
+ result
+ .addRunClasspathClasses(I.class)
+ .addRunClasspathClassFileData(removeFooMethod(Base.class)))
+ .applyIf(
+ !parameters.canUseDefaultAndStaticInterfaceMethods(),
+ result ->
+ result
+ .addRunClasspathClasses(Base.class)
+ .addRunClasspathClassFileData(removeFooMethod(I.class)));
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult, boolean r8) {
+ if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ runResult.assertSuccessWithOutputLines("I::foo");
+ return;
+ }
+ // TODO(b/254510678): We should not rebind to I.foo
+ if (r8) {
+ runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ } else {
+ runResult.assertSuccessWithOutputLines("Base::foo");
+ }
+ }
+
+ private interface I {
+
+ // Introduced at a later api level.
+ default void foo() {
+ System.out.println("I::foo");
+ }
+ }
+
+ public abstract static class Base {
+
+ // Was present until moved into I at some api level.
+ public void foo() {
+ System.out.println("Base::foo");
+ }
+ }
+
+ public static class ProgramClass extends Base implements I {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callFoo(new ProgramClass());
+ }
+
+ private static void callFoo(ProgramClass clazz) {
+ clazz.foo();
+ }
+ }
+}