blob: 3ba706d9b2212e64abebc5682c4cc83b9c208af6 [file] [log] [blame]
// 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.desugar;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.DesugarTestConfiguration;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.cf.CfVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
public class InterfaceInvokePrivateTest extends TestBase implements Opcodes {
@Parameter(0)
public TestParameters parameters;
@Parameter(1)
public CfVersion inputCfVersion;
@Parameterized.Parameters(name = "{0}, Input CfVersion = {1})")
public static Iterable<?> data() {
return buildParameters(
getTestParameters().withCfRuntimes().withDexRuntimes().withAllApiLevelsAlsoForCf().build(),
CfVersion.rangeInclusive(CfVersion.V1_8, CfVersion.V15));
}
private static final String EXPECTED_OUTPUT = StringUtils.unixLines("Hello, world!", "21", "6");
private boolean isInputCfVersionSupported() {
return inputCfVersion.isLessThanOrEqualTo(
CfVersion.fromRaw(parameters.getRuntime().asCf().getVm().getClassfileVersion()));
}
@Test
public void testReference() throws Exception {
assumeTrue(parameters.getRuntime().isCf());
assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForJvm()
.addProgramClassFileData(transformIToPrivate(inputCfVersion))
.addProgramClasses(TestRunner.class)
.run(parameters.getRuntime(), TestRunner.class)
.applyIf(
// Fails if input CF version is not supported.
!isInputCfVersionSupported(),
r ->
r.assertFailureWithErrorThatMatches(
containsString(
"more recent version of the Java Runtime (class file version "
+ inputCfVersion.toString())),
// Fails with ICCE on VMs prior to JDK 11.
parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11),
r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
// Succeeds on VMs from JDK 11 regardless of the CF version of the input.
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
}
@Test
public void testDesugar() throws Exception {
testForDesugaring(parameters)
.addProgramClassFileData(transformIToPrivate(inputCfVersion))
.addProgramClasses(TestRunner.class)
.run(parameters.getRuntime(), TestRunner.class)
.applyIf(
// Running un-desugared on a JVM with too high a CF version fails.
c ->
parameters.getRuntime().isCf()
&& !isInputCfVersionSupported()
&& DesugarTestConfiguration.isNotDesugared(c),
r ->
r.assertFailureWithErrorThatMatches(
containsString(
"more recent version of the Java Runtime (class file version "
+ inputCfVersion.toString())),
// Running without interface method desugaring on a JVM with a supported CF version
// fails on pre JVM 11. On JVM 11 and above this succeeds even of the input CF version
// is below 55.
c ->
parameters.getRuntime().isCf()
&& parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11)
&& (DesugarTestConfiguration.isNotDesugared(c)
|| (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
&& c == DesugarTestConfiguration.D8_DEX)),
r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
// All other conditions succeed.
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
}
@Test
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClassFileData(transformIToPrivate(inputCfVersion))
.addProgramClasses(TestRunner.class)
.addKeepMainRule(TestRunner.class)
// TODO(b/185463156): Not keeping I and its members will "fix" the ICCE for all runtimes.
.addKeepClassAndMembersRules(I.class)
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), TestRunner.class)
.applyIf(
// Running un-desugared on a JVM with too high a CF version fails.
parameters.getRuntime().isCf() && !isInputCfVersionSupported(),
r ->
r.assertFailureWithErrorThatMatches(
containsString(
"more recent version of the Java Runtime (class file version "
+ inputCfVersion.toString())),
// Running on a JVM with a supported CF version fails on pre JVM 11. On
// JVM 11 and above this succeeds even of the input CF version is below 55.
parameters.getRuntime().isCf()
&& parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11),
r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class),
// All other conditions succeed.
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
}
private byte[] transformIToPrivate(CfVersion version) throws NoSuchMethodException, IOException {
return transformer(I.class)
.setVersion(version)
.setPrivate(I.class.getDeclaredMethod("privateHello"))
.transformMethodInsnInMethod(
"hello",
((opcode, owner, name, descriptor, isInterface, continuation) -> {
continuation.visitMethodInsn(
name.equals("privateHello") ? Opcodes.INVOKEINTERFACE : opcode,
owner,
name,
descriptor,
isInterface);
}))
.transform();
}
interface I {
/* private */ default String privateHello() {
return "Hello, world!";
}
/* private */ default String privateHello(int i1, int i2, int i3, int i4, int i5, int i6) {
return "" + (i1 + i2 + i3 + i4 + i5 + i6);
}
/* private */ default String privateHello(long l1, long l2, long l3) {
return "" + (l1 + l2 + l3);
}
default String hello() {
// The private method "privateHello" is called with invokeinterface.
return privateHello()
+ "\n"
+ privateHello(1, 2, 3, 4, 5, 6)
+ "\n"
+ privateHello(1L, 2L, 3L)
+ "\n";
}
}
public static class TestRunner implements I {
public static void main(String[] args) {
System.out.print(new TestRunner().hello());
}
}
}