|  | // Copyright (c) 2018, 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.naming; | 
|  |  | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed; | 
|  | import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed; | 
|  | import static org.hamcrest.CoreMatchers.not; | 
|  | import static org.hamcrest.MatcherAssert.assertThat; | 
|  |  | 
|  | import com.android.tools.r8.R8TestCompileResult; | 
|  | import com.android.tools.r8.R8TestRunResult; | 
|  | import com.android.tools.r8.TestBase; | 
|  | import com.android.tools.r8.TestParameters; | 
|  | import com.android.tools.r8.TestParametersCollection; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.utils.codeinspector.ClassSubject; | 
|  | import com.android.tools.r8.utils.codeinspector.CodeInspector; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  | import org.objectweb.asm.ClassWriter; | 
|  | import org.objectweb.asm.FieldVisitor; | 
|  | import org.objectweb.asm.Label; | 
|  | import org.objectweb.asm.MethodVisitor; | 
|  | import org.objectweb.asm.Opcodes; | 
|  | import org.objectweb.asm.Type; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class EnumMinification extends TestBase { | 
|  |  | 
|  | private final TestParameters parameters; | 
|  |  | 
|  | @Parameters(name = "{0}") | 
|  | public static TestParametersCollection data() { | 
|  | return getTestParameters().withAllRuntimesAndApiLevels().build(); | 
|  | } | 
|  |  | 
|  | public EnumMinification(TestParameters parameters) { | 
|  | this.parameters = parameters; | 
|  | } | 
|  |  | 
|  | private R8TestCompileResult compile(Class<?> mainClass, byte[] enumClassFile) throws Exception { | 
|  | return testForR8(parameters.getBackend()) | 
|  | .addProgramClasses(mainClass) | 
|  | .addProgramClassFileData(enumClassFile) | 
|  | .addKeepMainRule(mainClass) | 
|  | .addKeepRules("-neverinline enum * extends java.lang.Enum { valueOf(...); }") | 
|  | .enableProguardTestOptions() | 
|  | .setMinApi(parameters.getApiLevel()) | 
|  | .compile(); | 
|  | } | 
|  |  | 
|  | public void runTest( | 
|  | Class<?> mainClass, byte[] enumClass, String enumTypeName, boolean valueOfKept) | 
|  | throws Exception { | 
|  | R8TestRunResult result = | 
|  | compile(mainClass, enumClass) | 
|  | .run(parameters.getRuntime(), mainClass) | 
|  | .assertSuccessWithOutput("VALUE1"); | 
|  |  | 
|  | CodeInspector inspector = result.inspector(); | 
|  | ClassSubject clazz = inspector.clazz(enumTypeName); | 
|  | // The class and fields - including the method valueOf - can be renamed. Only the values() | 
|  | // method needs to be. | 
|  | assertThat(clazz, isPresentAndRenamed()); | 
|  | assertThat(clazz.uniqueFieldWithName("VALUE1"), isAbsent()); | 
|  | assertThat(clazz.uniqueFieldWithName("VALUE2"), isAbsent()); | 
|  | assertThat(clazz.uniqueFieldWithName("$VALUES"), isPresentAndRenamed()); | 
|  | assertThat( | 
|  | clazz.uniqueMethodWithName("valueOf"), | 
|  | valueOfKept ? isPresentAndRenamed() : not(isPresent())); | 
|  | assertThat(clazz.uniqueMethodWithName("values"), isPresentAndNotRenamed()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void test() throws Exception { | 
|  | runTest(Main.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), true); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testAsmDump() throws Exception { | 
|  | runTest(Main.class, EnumDump.dump(true), "com.android.tools.r8.naming.Enum", true); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testWithoutValuesMethod() throws Exception { | 
|  | // This should not fail even if the values method is not present. | 
|  | compile(Main.class, EnumDump.dump(false)); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testJavaLangEnumValueOf() throws Exception { | 
|  | runTest(Main2.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), false); | 
|  | } | 
|  | } | 
|  |  | 
|  | class Main { | 
|  |  | 
|  | public static void main(String[] args) { | 
|  | Enum e = Enum.valueOf("VALUE1"); | 
|  | System.out.print(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | enum Enum { | 
|  | VALUE1, | 
|  | VALUE2 | 
|  | } | 
|  |  | 
|  | class Main2 { | 
|  | public static void main(String[] args) { | 
|  | // Use java.lang.Enum.valueOf instead of com.android.tools.r8.naming.Enum.valueOf. | 
|  | System.out.print(java.lang.Enum.valueOf(Enum.class, "VALUE1")); | 
|  | } | 
|  | } | 
|  | /* Dump of javac generated code from the following enum class (the one just above): | 
|  | * | 
|  | *  package com.android.tools.r8.naming; | 
|  | * | 
|  | *  enum Enum { | 
|  | *    VALUE1, | 
|  | *    VALUE2 | 
|  | *  } | 
|  | * | 
|  | */ | 
|  | class EnumDump implements Opcodes { | 
|  |  | 
|  | public static byte[] dump(boolean includeValuesMethod) { | 
|  | ClassWriter classWriter = new ClassWriter(0); | 
|  | FieldVisitor fieldVisitor; | 
|  | MethodVisitor methodVisitor; | 
|  |  | 
|  | classWriter.visit( | 
|  | V1_8, | 
|  | ACC_FINAL | ACC_SUPER | ACC_ENUM, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "Ljava/lang/Enum<Lcom/android/tools/r8/naming/Enum;>;", | 
|  | "java/lang/Enum", | 
|  | null); | 
|  |  | 
|  | classWriter.visitSource("EnumMinification.java", null); | 
|  |  | 
|  | { | 
|  | fieldVisitor = | 
|  | classWriter.visitField( | 
|  | ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM, | 
|  | "VALUE1", | 
|  | "Lcom/android/tools/r8/naming/Enum;", | 
|  | null, | 
|  | null); | 
|  | fieldVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | fieldVisitor = | 
|  | classWriter.visitField( | 
|  | ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM, | 
|  | "VALUE2", | 
|  | "Lcom/android/tools/r8/naming/Enum;", | 
|  | null, | 
|  | null); | 
|  | fieldVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | fieldVisitor = | 
|  | classWriter.visitField( | 
|  | ACC_PRIVATE | ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC, | 
|  | "$VALUES", | 
|  | "[Lcom/android/tools/r8/naming/Enum;", | 
|  | null, | 
|  | null); | 
|  | fieldVisitor.visitEnd(); | 
|  | } | 
|  | if (includeValuesMethod) { | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod( | 
|  | ACC_PUBLIC | ACC_STATIC, | 
|  | "values", | 
|  | "()[Lcom/android/tools/r8/naming/Enum;", | 
|  | null, | 
|  | null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(72, label0); | 
|  | methodVisitor.visitFieldInsn( | 
|  | GETSTATIC, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "$VALUES", | 
|  | "[Lcom/android/tools/r8/naming/Enum;"); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKEVIRTUAL, | 
|  | "[Lcom/android/tools/r8/naming/Enum;", | 
|  | "clone", | 
|  | "()Ljava/lang/Object;", | 
|  | false); | 
|  | methodVisitor.visitTypeInsn(CHECKCAST, "[Lcom/android/tools/r8/naming/Enum;"); | 
|  | methodVisitor.visitInsn(ARETURN); | 
|  | methodVisitor.visitMaxs(1, 0); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod( | 
|  | ACC_PUBLIC | ACC_STATIC, | 
|  | "valueOf", | 
|  | "(Ljava/lang/String;)Lcom/android/tools/r8/naming/Enum;", | 
|  | null, | 
|  | null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(72, label0); | 
|  | methodVisitor.visitLdcInsn(Type.getType("Lcom/android/tools/r8/naming/Enum;")); | 
|  | methodVisitor.visitVarInsn(ALOAD, 0); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESTATIC, | 
|  | "java/lang/Enum", | 
|  | "valueOf", | 
|  | "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", | 
|  | false); | 
|  | methodVisitor.visitTypeInsn(CHECKCAST, "com/android/tools/r8/naming/Enum"); | 
|  | methodVisitor.visitInsn(ARETURN); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 0); | 
|  | methodVisitor.visitMaxs(2, 1); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = | 
|  | classWriter.visitMethod(ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", "()V", null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(72, label0); | 
|  | methodVisitor.visitVarInsn(ALOAD, 0); | 
|  | methodVisitor.visitVarInsn(ALOAD, 1); | 
|  | methodVisitor.visitVarInsn(ILOAD, 2); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V", false); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLocalVariable( | 
|  | "this", "Lcom/android/tools/r8/naming/Enum;", null, label0, label1, 0); | 
|  | methodVisitor.visitMaxs(3, 3); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | { | 
|  | methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); | 
|  | methodVisitor.visitCode(); | 
|  | Label label0 = new Label(); | 
|  | methodVisitor.visitLabel(label0); | 
|  | methodVisitor.visitLineNumber(73, label0); | 
|  | methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum"); | 
|  | methodVisitor.visitInsn(DUP); | 
|  | methodVisitor.visitLdcInsn("VALUE1"); | 
|  | methodVisitor.visitInsn(ICONST_0); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESPECIAL, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "<init>", | 
|  | "(Ljava/lang/String;I)V", | 
|  | false); | 
|  | methodVisitor.visitFieldInsn( | 
|  | PUTSTATIC, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "VALUE1", | 
|  | "Lcom/android/tools/r8/naming/Enum;"); | 
|  | Label label1 = new Label(); | 
|  | methodVisitor.visitLabel(label1); | 
|  | methodVisitor.visitLineNumber(74, label1); | 
|  | methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum"); | 
|  | methodVisitor.visitInsn(DUP); | 
|  | methodVisitor.visitLdcInsn("VALUE2"); | 
|  | methodVisitor.visitInsn(ICONST_1); | 
|  | methodVisitor.visitMethodInsn( | 
|  | INVOKESPECIAL, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "<init>", | 
|  | "(Ljava/lang/String;I)V", | 
|  | false); | 
|  | methodVisitor.visitFieldInsn( | 
|  | PUTSTATIC, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "VALUE2", | 
|  | "Lcom/android/tools/r8/naming/Enum;"); | 
|  | Label label2 = new Label(); | 
|  | methodVisitor.visitLabel(label2); | 
|  | methodVisitor.visitLineNumber(72, label2); | 
|  | methodVisitor.visitInsn(ICONST_2); | 
|  | methodVisitor.visitTypeInsn(ANEWARRAY, "com/android/tools/r8/naming/Enum"); | 
|  | methodVisitor.visitInsn(DUP); | 
|  | methodVisitor.visitInsn(ICONST_0); | 
|  | methodVisitor.visitFieldInsn( | 
|  | GETSTATIC, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "VALUE1", | 
|  | "Lcom/android/tools/r8/naming/Enum;"); | 
|  | methodVisitor.visitInsn(AASTORE); | 
|  | methodVisitor.visitInsn(DUP); | 
|  | methodVisitor.visitInsn(ICONST_1); | 
|  | methodVisitor.visitFieldInsn( | 
|  | GETSTATIC, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "VALUE2", | 
|  | "Lcom/android/tools/r8/naming/Enum;"); | 
|  | methodVisitor.visitInsn(AASTORE); | 
|  | methodVisitor.visitFieldInsn( | 
|  | PUTSTATIC, | 
|  | "com/android/tools/r8/naming/Enum", | 
|  | "$VALUES", | 
|  | "[Lcom/android/tools/r8/naming/Enum;"); | 
|  | methodVisitor.visitInsn(RETURN); | 
|  | methodVisitor.visitMaxs(4, 0); | 
|  | methodVisitor.visitEnd(); | 
|  | } | 
|  | classWriter.visitEnd(); | 
|  |  | 
|  | return classWriter.toByteArray(); | 
|  | } | 
|  | } |