| // Copyright (c) 2020, 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.enumunboxing; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| |
| import com.android.tools.r8.R8TestCompileResult; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import java.util.List; |
| import org.junit.Assume; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class EnumToStringLibTest extends EnumUnboxingTestBase { |
| |
| private final TestParameters parameters; |
| private final boolean enumValueOptimization; |
| private final EnumKeepRules enumKeepRules; |
| private final boolean enumUnboxing; |
| |
| @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2} unbox: {3}") |
| public static List<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withDexRuntimes().withAllApiLevels().build(), |
| BooleanUtils.values(), |
| getAllEnumKeepRules(), |
| BooleanUtils.values()); |
| } |
| |
| public EnumToStringLibTest( |
| TestParameters parameters, |
| boolean enumValueOptimization, |
| EnumKeepRules enumKeepRules, |
| boolean enumUnboxing) { |
| this.parameters = parameters; |
| this.enumValueOptimization = enumValueOptimization; |
| this.enumKeepRules = enumKeepRules; |
| this.enumUnboxing = enumUnboxing; |
| } |
| |
| @Test |
| public void testToStringLib() throws Exception { |
| Assume.assumeFalse( |
| "The test rely on valueOf, so only studio or snap keep rules are valid.", |
| enumKeepRules == EnumKeepRules.NONE); |
| // Compile the lib cf to cf. |
| R8TestCompileResult javaLibShrunk = compileLibrary(); |
| assertEnumFieldsMinified(javaLibShrunk.inspector()); |
| // Compile the program with the lib. |
| R8TestCompileResult compile = |
| testForR8(parameters.getBackend()) |
| .addProgramClasses(AlwaysCorrectProgram.class, AlwaysCorrectProgram2.class) |
| .addProgramFiles(javaLibShrunk.writeToZip()) |
| .addKeepMainRule(AlwaysCorrectProgram.class) |
| .addKeepMainRule(AlwaysCorrectProgram2.class) |
| .addKeepRules(enumKeepRules.getKeepRules()) |
| .addKeepAttributeLineNumberTable() |
| .addOptionsModification( |
| options -> { |
| options.enableEnumUnboxing = enumUnboxing; |
| options.enableEnumValueOptimization = enumValueOptimization; |
| options.enableEnumSwitchMapRemoval = enumValueOptimization; |
| options.testing.enableEnumUnboxingDebugLogs = enumUnboxing; |
| }) |
| .allowDiagnosticInfoMessages(enumUnboxing) |
| .setMinApi(parameters.getApiLevel()) |
| .compile(); |
| compile |
| .run(parameters.getRuntime(), AlwaysCorrectProgram.class) |
| .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2", "0", "1", "2"); |
| compile |
| .run(parameters.getRuntime(), AlwaysCorrectProgram2.class) |
| .assertSuccessWithOutputLines("0", "1", "2", "0", "1", "2"); |
| } |
| |
| private void assertEnumFieldsMinified(CodeInspector codeInspector) throws Exception { |
| ClassSubject clazz = codeInspector.clazz(ToStringLib.LibEnum.class); |
| assertThat(clazz, isPresent()); |
| for (String fieldName : new String[] {"COFFEE", "BEAN", "SUGAR"}) { |
| assertThat( |
| codeInspector.field(ToStringLib.LibEnum.class.getField(fieldName)), |
| isPresentAndRenamed()); |
| } |
| } |
| |
| private R8TestCompileResult compileLibrary() throws Exception { |
| return testForR8(Backend.CF) |
| .addProgramClasses(ToStringLib.class, ToStringLib.LibEnum.class) |
| .addKeepRules(enumKeepRules.getKeepRules()) |
| // TODO(b/160535629): Work-around on some optimizations relying on $VALUES name. |
| .addKeepRules( |
| "-keep enum " |
| + ToStringLib.LibEnum.class.getName() |
| + " { static " |
| + ToStringLib.LibEnum.class.getName() |
| + "[] $VALUES; }") |
| .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization)) |
| .addKeepMethodRules( |
| Reference.methodFromMethod( |
| ToStringLib.class.getDeclaredMethod("lookupByName", String.class)), |
| Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("getCoffee")), |
| Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("getBean")), |
| Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("getSugar")), |
| Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directCoffee")), |
| Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directBean")), |
| Reference.methodFromMethod(ToStringLib.class.getDeclaredMethod("directSugar"))) |
| .addKeepClassRules(ToStringLib.LibEnum.class) |
| .setMinApi(parameters.getApiLevel()) |
| .compile(); |
| } |
| |
| // This class emulates a library with the three public methods getEnumXXX. |
| public static class ToStringLib { |
| |
| // We pick names here that we assume won't be picked by the minifier (i.e., not a,b,c). |
| public enum LibEnum { |
| COFFEE, |
| BEAN, |
| SUGAR; |
| } |
| |
| // If there is a keep rule on LibEnum fields, then ToStringLib.lookupByName("COFFEE") |
| // should answer 0, else, the behavior of ToStringLib.lookupByName("COFFEE") is undefined. |
| // ToStringLib.lookupByName(LibEnum.COFFEE.toString()) should always answer 0, no matter |
| // what keep rules are on LibEnum. |
| public static int lookupByName(String key) { |
| if (key == null) { |
| return -1; |
| } else if (key.contains(LibEnum.COFFEE.name())) { |
| return LibEnum.COFFEE.ordinal(); |
| } else if (key.contains(LibEnum.BEAN.name())) { |
| return LibEnum.BEAN.ordinal(); |
| } else if (key.contains(LibEnum.SUGAR.name())) { |
| return LibEnum.SUGAR.ordinal(); |
| } else { |
| return -2; |
| } |
| } |
| |
| // The following method should always return 0, no matter what keep rules are on LibEnum. |
| public static int directCoffee() { |
| return LibEnum.valueOf(LibEnum.COFFEE.toString()).ordinal(); |
| } |
| |
| public static int directBean() { |
| return LibEnum.valueOf(LibEnum.BEAN.toString()).ordinal(); |
| } |
| |
| public static int directSugar() { |
| return LibEnum.valueOf(LibEnum.SUGAR.toString()).ordinal(); |
| } |
| |
| public static LibEnum getCoffee() { |
| return LibEnum.COFFEE; |
| } |
| |
| public static LibEnum getBean() { |
| return LibEnum.BEAN; |
| } |
| |
| public static LibEnum getSugar() { |
| return LibEnum.SUGAR; |
| } |
| } |
| |
| // The next two classes emulate a program using the ToStringLib library. |
| public static class AlwaysCorrectProgram { |
| |
| public static void main(String[] args) { |
| System.out.println(ToStringLib.directCoffee()); |
| System.out.println(ToStringLib.directBean()); |
| System.out.println(ToStringLib.directSugar()); |
| System.out.println(ToStringLib.lookupByName(ToStringLib.getCoffee().toString())); |
| System.out.println(ToStringLib.lookupByName(ToStringLib.getBean().toString())); |
| System.out.println(ToStringLib.lookupByName(ToStringLib.getSugar().toString())); |
| System.out.println(ToStringLib.LibEnum.valueOf(ToStringLib.getCoffee().toString()).ordinal()); |
| System.out.println(ToStringLib.LibEnum.valueOf(ToStringLib.getBean().toString()).ordinal()); |
| System.out.println(ToStringLib.LibEnum.valueOf(ToStringLib.getSugar().toString()).ordinal()); |
| } |
| } |
| |
| public static class AlwaysCorrectProgram2 { |
| |
| public static void main(String[] args) { |
| System.out.println(ToStringLib.lookupByName("COFFEE")); |
| System.out.println(ToStringLib.lookupByName("BEAN")); |
| System.out.println(ToStringLib.lookupByName("SUGAR")); |
| System.out.println(ToStringLib.LibEnum.valueOf("COFFEE").ordinal()); |
| System.out.println(ToStringLib.LibEnum.valueOf("BEAN").ordinal()); |
| System.out.println(ToStringLib.LibEnum.valueOf("SUGAR").ordinal()); |
| } |
| } |
| } |