|  | // 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 MultipleNamedConstantDynamicTest extends TestBase { | 
|  |  | 
|  | @Parameter(0) | 
|  | 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 testDesugaring() 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 testR8() throws Exception { | 
|  | assumeTrue( | 
|  | parameters.getRuntime().isDex() || 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, "myConstant", "constantF", Object.class) | 
|  | .transformConstStringToConstantDynamic( | 
|  | "condy2", A.class, "myConstant", "constantF", Object.class) | 
|  | .transformConstStringToConstantDynamic( | 
|  | "condy3", A.class, "myConstant", "constantG", Object.class) | 
|  | .transformConstStringToConstantDynamic( | 
|  | "condy4", A.class, "myConstant", "constantG", 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 myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) { | 
|  | // TODO(b/178172809): Use a un-moddeled library method to prevent R8 from seeing that name | 
|  | //  is unused. | 
|  | name.toCharArray(); | 
|  | return new Object(); | 
|  | } | 
|  | } | 
|  | } |