Update CONSTANT_Dynamic desugaring
* Handle some invalid bootstrap methods
* Test multiple constants with different bootstrap methods
in same class
Bug: 178172809
Change-Id: I95042912e16b67e7ceaa13e0e6047af1a396905d
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
index bd2e603..2933e4d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.desugar.constantdynamic;
import static org.objectweb.asm.Opcodes.GETSTATIC;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
@@ -23,6 +24,7 @@
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
@@ -151,10 +153,19 @@
.build()));
}
- private void invokeBootstrapMethod(
- ConstantDynamicReference reference, ImmutableList.Builder<CfInstruction> instructions) {
+ private void invokeBootstrapMethod(ImmutableList.Builder<CfInstruction> instructions) {
assert reference.getBootstrapMethod().type.isInvokeStatic();
+ DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
+ DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
+ // TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported.
+ instructions.add(new CfConstNull());
+ instructions.add(new CfConstString(reference.getName()));
+ instructions.add(new CfConstClass(reference.getType()));
+ instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false));
+ instructions.add(new CfCheckCast(reference.getType()));
+ }
+ private CfCode generateGetterCode(SyntheticProgramClassBuilder builder) {
// TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported.
DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
@@ -162,25 +173,24 @@
appView
.appInfoForDesugaring()
.resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
- if (resolution.isFailedResolution()) {
- // TODO(b/178172809): Generate code which throws ICCE.
+ if ((resolution.isSingleResolution()
+ && resolution.asSingleResolution().getResolvedMethod().isStatic())
+ || resolution.isFailedResolution()) {
+ if (resolution.isSingleResolution()) {
+ // Ensure that the bootstrap method is accessible from the generated class.
+ SingleResolutionResult result = resolution.asSingleResolution();
+ MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
+ flags.unsetPrivate();
+ flags.setPublic();
+ }
+ return generateGetterCodeInvokingBootstrapMethod(builder);
+ } else {
+ // Unconditionally throw ICCE as the RI.
+ return generateGetterCodeThrowingICCE(builder);
}
- SingleResolutionResult result = resolution.asSingleResolution();
- assert result.getResolvedMethod().isStatic();
- assert result.getResolvedHolder().isProgramClass();
- instructions.add(new CfConstNull());
- instructions.add(new CfConstString(reference.getName()));
- instructions.add(new CfConstClass(reference.getType()));
- instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false));
- instructions.add(new CfCheckCast(reference.getType()));
-
- // Ensure that the bootstrap method is accessible from the generated class.
- MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
- flags.unsetPrivate();
- flags.setPublic();
}
- private CfCode generateGetterCode(SyntheticProgramClassBuilder builder) {
+ private CfCode generateGetterCodeInvokingBootstrapMethod(SyntheticProgramClassBuilder builder) {
int maxStack = 3;
int maxLocals = 2;
ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
@@ -205,7 +215,7 @@
instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrueSecond));
- invokeBootstrapMethod(reference, instructions);
+ invokeBootstrapMethod(instructions);
instructions.add(new CfFieldInstruction(PUTSTATIC, constantValueField));
instructions.add(new CfConstNumber(1, ValueType.INT));
instructions.add(new CfFieldInstruction(PUTSTATIC, initializedValueField));
@@ -262,6 +272,33 @@
localVariables);
}
+ private CfCode generateGetterCodeThrowingICCE(SyntheticProgramClassBuilder builder) {
+ int maxStack = 2;
+ int maxLocals = 0;
+ ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+ ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+ ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
+ DexItemFactory factory = builder.getFactory();
+ instructions.add(new CfNew(factory.icceType));
+ instructions.add(new CfStackInstruction(Opcode.Dup));
+ instructions.add(
+ new CfInvoke(
+ INVOKESPECIAL,
+ factory.createMethod(
+ factory.icceType,
+ factory.createProto(factory.voidType),
+ factory.constructorMethodName),
+ false));
+ instructions.add(new CfThrow());
+ return new CfCode(
+ builder.getType(),
+ maxStack,
+ maxLocals,
+ instructions.build(),
+ tryCatchRanges,
+ localVariables);
+ }
+
public final DexProgramClass getConstantDynamicProgramClass() {
assert clazz != null;
return clazz;
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java
new file mode 100644
index 0000000..12ee318
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodNotFoundConstantDynamicTest.java
@@ -0,0 +1,135 @@
+// 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.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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 java.io.IOException;
+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 BootstrapMethodNotFoundConstantDynamicTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final Class<?> MAIN_CLASS = A.class;
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForJvm()
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+
+ @Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ })
+ .applyIf(
+ DesugarTestConfiguration::isDesugared,
+ r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(A.class)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+ }
+
+ private byte[] getTransformedClasses() throws IOException {
+ return transformer(A.class)
+ .setVersion(CfVersion.V11)
+ .transformConstStringToConstantDynamic(
+ "condy1", A.class, "methodNotFound", "constantName", Object.class)
+ .transform();
+ }
+
+ public static class A {
+
+ public static Object f() {
+ return "condy1"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static void main(String[] args) {
+ System.out.println(f() != null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodPrivateConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodPrivateConstantDynamicTest.java
new file mode 100644
index 0000000..2df349a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodPrivateConstantDynamicTest.java
@@ -0,0 +1,133 @@
+// 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.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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 java.io.IOException;
+import java.lang.invoke.MethodHandles;
+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 BootstrapMethodPrivateConstantDynamicTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final Class<?> MAIN_CLASS = A.class;
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForJvm()
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ @Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ },
+ r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(A.class)
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+ }
+
+ private byte[] getTransformedClasses() throws IOException {
+ return transformer(A.class)
+ .setVersion(CfVersion.V11)
+ .transformConstStringToConstantDynamic(
+ "condy1", A.class, "myConstant", "constantName", Object.class)
+ .transform();
+ }
+
+ public static class A {
+
+ public static Object f() {
+ return "condy1"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static void main(String[] args) {
+ System.out.println(f() != null);
+ }
+
+ private Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+ return new Object();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodVirtualConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodVirtualConstantDynamicTest.java
new file mode 100644
index 0000000..52bd722
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BootstrapMethodVirtualConstantDynamicTest.java
@@ -0,0 +1,133 @@
+// 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.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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 java.io.IOException;
+import java.lang.invoke.MethodHandles;
+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 BootstrapMethodVirtualConstantDynamicTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final Class<?> MAIN_CLASS = A.class;
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForJvm()
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ @Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ },
+ r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(A.class)
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+ }
+
+ private byte[] getTransformedClasses() throws IOException {
+ return transformer(A.class)
+ .setVersion(CfVersion.V11)
+ .transformConstStringToConstantDynamic(
+ "condy1", A.class, "myConstant", "constantName", Object.class)
+ .transform();
+ }
+
+ public static class A {
+
+ public static Object f() {
+ return "condy1"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static void main(String[] args) {
+ System.out.println(f() != null);
+ }
+
+ public Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+ return new Object();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleBootstrapMethodConstantDynamicTest.java
new file mode 100644
index 0000000..3c54e23
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleBootstrapMethodConstantDynamicTest.java
@@ -0,0 +1,168 @@
+// 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.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+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 java.lang.invoke.MethodHandles;
+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 MultipleBootstrapMethodConstantDynamicTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("true", "true", "true", "true", "true");
+ private static final Class<?> MAIN_CLASS = A.class;
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+ assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForJvm()
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void TestD8Cf() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .applyIf(
+ // When not desugaring the CF code requires JDK 11.
+ DesugarTestConfiguration::isNotDesugared,
+ r -> {
+ if (parameters.isCfRuntime()
+ && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+ }
+ })
+ .applyIf(
+ DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(A.class)
+ // TODO(b/198142613): There should not be a warnings on class references which are
+ // desugared away.
+ .applyIf(
+ parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+ b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+ // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ r.compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Unsupported dynamic constant (not desugaring)")));
+ }));
+ },
+ r ->
+ r.run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ private byte[] getTransformedClasses() throws IOException {
+ return transformer(A.class)
+ .setVersion(CfVersion.V11)
+ .transformConstStringToConstantDynamic(
+ "condy1", A.class, "myConstant1", "constantName", Object.class)
+ .transformConstStringToConstantDynamic(
+ "condy2", A.class, "myConstant1", "constantName", Object.class)
+ .transformConstStringToConstantDynamic(
+ "condy3", A.class, "myConstant2", "constantName", Object.class)
+ .transformConstStringToConstantDynamic(
+ "condy4", A.class, "myConstant2", "constantName", Object.class)
+ .transform();
+ }
+
+ public static class A {
+
+ public static Object f1() {
+ return "condy1"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static Object f2() {
+ return "condy2"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static Object g1() {
+ return "condy3"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static Object g2() {
+ return "condy4"; // Will be transformed to Constant_DYNAMIC.
+ }
+
+ public static void main(String[] args) {
+ System.out.println(f1() != null);
+ System.out.println(f1() == f2());
+ System.out.println(g1() != null);
+ System.out.println(g1() == g2());
+ System.out.println(f1() != g2());
+ }
+
+ private static Object myConstant1(MethodHandles.Lookup lookup, String name, Class<?> type) {
+ return new Object();
+ }
+
+ private static Object myConstant2(MethodHandles.Lookup lookup, String name, Class<?> type) {
+ return new Object();
+ }
+ }
+}