Add test for having bridge with symbolic reference to super-interface
Bug: b/259227990
Change-Id: Ie76a0b2d31c7c2c3b7cd2696eb1699446b2d72bc
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java
new file mode 100644
index 0000000..2b41739
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java
@@ -0,0 +1,164 @@
+// 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 org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 MemberRebindingAmbiguousDispatchTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean abstractMethodOnSuperClass;
+
+ @Parameter(2)
+ public boolean interfaceAsSymbolicReference;
+
+ @Parameters(name = "{0}, abstractMethodOnSuperClass: {1}, interfaceAsSymbolicReference {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ private void setupInput(TestBuilder<?, ?> testBuilder) {
+ testBuilder
+ .addProgramClasses(Main.class, SuperInterface.class)
+ .applyIf(
+ abstractMethodOnSuperClass,
+ b -> b.addProgramClassFileData(getSuperClassWithFooAsAbstract()),
+ b -> b.addProgramClasses(SuperClass.class))
+ .applyIf(
+ interfaceAsSymbolicReference,
+ b -> b.addProgramClassFileData(getProgramClassWithInvokeToInterface()),
+ b -> b.addProgramClasses(ProgramClass.class));
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .apply(this::setupInput)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private boolean desugaringWithoutSupport() {
+ return parameters.isDexRuntime()
+ && interfaceAsSymbolicReference
+ && !parameters.canUseDefaultAndStaticInterfaceMethods();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeFalse(desugaringWithoutSupport());
+ testForR8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8AssertionError() {
+ assumeTrue(desugaringWithoutSupport());
+ // TODO(b/259227990): We should not fail compilation.
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertErrorThatMatches(
+ DiagnosticsMatcher.diagnosticException(AssertionError.class))));
+ }
+
+ private void checkOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isDalvik()
+ && interfaceAsSymbolicReference) {
+ result.assertFailureWithErrorThatThrows(VerifyError.class);
+ } else if (parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)
+ && interfaceAsSymbolicReference
+ && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ result.assertFailureWithErrorThatThrows(ClassNotFoundException.class);
+ } else if (abstractMethodOnSuperClass || interfaceAsSymbolicReference) {
+ result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ } else {
+ result.assertSuccessWithOutputLines("SuperClass::foo");
+ }
+ }
+
+ private byte[] getSuperClassWithFooAsAbstract() throws Exception {
+ return transformer(SuperClassAbstract.class)
+ .setClassDescriptor(descriptor(SuperClass.class))
+ .transform();
+ }
+
+ private byte[] getProgramClassWithInvokeToInterface() throws Exception {
+ return transformer(ProgramClass.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, visitor) ->
+ visitor.visitMethodInsn(
+ opcode, binaryName(SuperInterface.class), name, descriptor, true))
+ .transform();
+ }
+
+ public abstract static class SuperClassAbstract {
+
+ public abstract void foo();
+ }
+
+ public abstract static class SuperClass {
+
+ public void foo() {
+ System.out.println("SuperClass::foo");
+ }
+ }
+
+ public interface SuperInterface {
+
+ void foo();
+ }
+
+ public static class ProgramClass extends SuperClass implements SuperInterface {
+
+ @Override
+ public void foo() {
+ super.foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new ProgramClass().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java
new file mode 100644
index 0000000..81ac099
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java
@@ -0,0 +1,152 @@
+// 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 org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 MemberRebindingAmbiguousDispatchToLibraryTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean abstractMethodOnSuperClass;
+
+ @Parameter(2)
+ public boolean interfaceAsSymbolicReference;
+
+ @Parameters(name = "{0}, abstractMethodOnSuperClass: {1}, interfaceAsSymbolicReference {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ private void setupInput(TestBuilder<?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getProgramClass())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(SuperInterface.class)
+ .addLibraryClassFileData(getSuperClass());
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .apply(this::setupInput)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathClasses(SuperInterface.class)
+ .addRunClasspathClassFileData(getSuperClass())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile()
+ .addRunClasspathClasses(SuperInterface.class)
+ .addRunClasspathClassFileData(getSuperClass())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private void checkOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime() && interfaceAsSymbolicReference) {
+ if (parameters.getDexRuntimeVersion().isDalvik()) {
+ result.assertFailureWithErrorThatThrows(VerifyError.class);
+ } else if (parameters.getDexRuntimeVersion().isOlderThan(Version.V7_0_0)) {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ } else {
+ // TODO(b/259227990): If the SuperClass.foo() is not abstract we produce a working program.
+ result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ }
+ } else if (abstractMethodOnSuperClass || interfaceAsSymbolicReference) {
+ result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ } else {
+ result.assertSuccessWithOutputLines("SuperClass::foo");
+ }
+ }
+
+ private byte[] getSuperClass() throws Exception {
+ return abstractMethodOnSuperClass
+ ? transformer(SuperClassAbstract.class)
+ .setClassDescriptor(descriptor(SuperClass.class))
+ .transform()
+ : transformer(SuperClass.class).transform();
+ }
+
+ private byte[] getProgramClass() throws Exception {
+ return interfaceAsSymbolicReference
+ ? transformer(ProgramClass.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, visitor) ->
+ visitor.visitMethodInsn(
+ opcode, binaryName(SuperInterface.class), name, descriptor, true))
+ .transform()
+ : transformer(ProgramClass.class).transform();
+ }
+
+ public abstract static class SuperClassAbstract {
+
+ public abstract void foo();
+ }
+
+ public abstract static class SuperClass {
+
+ public void foo() {
+ System.out.println("SuperClass::foo");
+ }
+ }
+
+ public interface SuperInterface {
+
+ void foo();
+ }
+
+ public static class ProgramClass extends SuperClass implements SuperInterface {
+
+ @Override
+ public void foo() {
+ super.foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new ProgramClass().foo();
+ }
+ }
+}