Merge "Extend reflective analysis to include java.lang.Enum.valueOf"
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 90a098a..10e960c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -217,6 +217,7 @@
   public final LongMethods longMethods = new LongMethods();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final ClassMethods classMethods = new ClassMethods();
+  public final EnumMethods enumMethods = new EnumMethods();
   public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
       new PrimitiveTypesBoxedTypeFields();
   public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
@@ -408,6 +409,20 @@
     }
   }
 
+  public class EnumMethods {
+
+    public DexMethod valueOf;
+
+    private EnumMethods() {
+      valueOf =
+          createMethod(
+              enumDescriptor,
+              valueOfMethodName,
+              enumDescriptor,
+              new DexString[] {classDescriptor, stringDescriptor});
+    }
+  }
+
   /**
    * All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
    * for the primitive type.
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index de42c18..0be9f91 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -68,6 +68,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -253,7 +254,6 @@
 
   private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
     items.entrySet().forEach(this::enqueueRootItem);
-    pinnedItems.addAll(items.keySet());
   }
 
   private void enqueueRootItem(Entry<DexDefinition, ProguardKeepRule> root) {
@@ -261,7 +261,10 @@
   }
 
   private void enqueueRootItem(DexDefinition item, ProguardKeepRule rule) {
-    KeepReason reason = KeepReason.dueToKeepRule(rule);
+    enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
+  }
+
+  private void enqueueRootItem(DexDefinition item, KeepReason reason) {
     if (item.isDexClass()) {
       DexClass clazz = item.asDexClass();
       workList.add(Action.markInstantiated(clazz, reason));
@@ -285,6 +288,7 @@
     } else {
       throw new IllegalArgumentException(item.toString());
     }
+    pinnedItems.add(item);
   }
 
   private void enqueueHolderIfDependentNonStaticMember(
@@ -391,6 +395,10 @@
         // Revisit the current method to implicitly add -keep rule for items with reflective access.
         pendingReflectiveUses.add(currentMethod);
       }
+      // See comment in handleJavaLangEnumValueOf.
+      if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
+        pendingReflectiveUses.add(currentMethod);
+      }
       if (!registerItemWithTarget(staticInvokes, method)) {
         return false;
       }
@@ -1150,6 +1158,25 @@
     }
   }
 
+  private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
+    DexType arrayOfEnumClass =
+        appInfo.dexItemFactory.createType(
+            appInfo.dexItemFactory.createString("[" + enumClass.type.toDescriptorString()));
+    DexProto proto = appInfo.dexItemFactory.createProto(arrayOfEnumClass);
+    return appInfo.dexItemFactory.createMethod(
+        enumClass.type, proto, appInfo.dexItemFactory.createString("values"));
+  }
+
+  private void markEnumValuesAsReachable(DexClass clazz, KeepReason reason) {
+    DexEncodedMethod valuesMethod = clazz.lookupMethod(generatedEnumValuesMethod(clazz));
+    if (valuesMethod != null) {
+      // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
+      // marking of not renaming is in the root set.
+      enqueueRootItem(valuesMethod, reason);
+      rootSet.noObfuscation.add(valuesMethod);
+    }
+  }
+
   private static void fillWorkList(Deque<DexType> worklist, DexType type) {
     if (type.isInterface()) {
       // We need to check if the method is shadowed by a class that directly implements
@@ -1534,15 +1561,23 @@
     DexType originHolder = method.method.holder;
     Origin origin = appInfo.originFor(originHolder);
     IRCode code = method.buildIR(appInfo, appView.graphLense(), options, origin);
-    code.instructionIterator().forEachRemaining(this::handleReflectiveBehavior);
+    Iterator<Instruction> iterator = code.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      handleReflectiveBehavior(method, instruction);
+    }
   }
 
-  private void handleReflectiveBehavior(Instruction instruction) {
+  private void handleReflectiveBehavior(DexEncodedMethod method, Instruction instruction) {
     if (!instruction.isInvokeMethod()) {
       return;
     }
     InvokeMethod invoke = instruction.asInvokeMethod();
     DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod == appInfo.dexItemFactory.enumMethods.valueOf) {
+      handleJavaLangEnumValueOf(method, invoke);
+      return;
+    }
     if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
       return;
     }
@@ -1577,6 +1612,20 @@
     }
   }
 
+  private void handleJavaLangEnumValueOf(DexEncodedMethod method, InvokeMethod invoke) {
+    // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
+    // access the values() method of the enum class passed as the first argument. The method
+    // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
+    // call this method.
+    if (invoke.inValues().get(0).isConstClass()) {
+      DexClass clazz =
+          appInfo.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
+      if (clazz.accessFlags.isEnum() && clazz.superType == appInfo.dexItemFactory.enumType) {
+        markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
+      }
+    }
+  }
+
   private static class Action {
 
     final Kind kind;
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index a8708eb..66cd45e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -4,17 +4,19 @@
 
 package com.android.tools.r8.naming;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -22,6 +24,12 @@
 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 {
@@ -37,29 +45,57 @@
     this.backend = backend;
   }
 
+  private AndroidApp buildApp(Class<?> mainClass, byte[] enumClassFile) throws Exception {
+    return ToolHelper.runR8(
+        R8Command.builder()
+            .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+            .addClassProgramData(enumClassFile, Origin.unknown())
+            .addProguardConfiguration(
+                ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+            .setProgramConsumer(emptyConsumer(backend))
+            .build());
+  }
+
+  public void runTest(
+      Class<?> mainClass, byte[] enumClass, String enumTypeName, boolean valueOfKept)
+      throws Exception {
+    AndroidApp output = buildApp(mainClass, enumClass);
+
+    CodeInspector inspector = new CodeInspector(output);
+    ClassSubject clazz = inspector.clazz(enumTypeName);
+    // The class and fields - including field $VALUES and method valueOf - can be renamed. Only
+    // the values() method needs to be
+    assertThat(clazz, isRenamed());
+    assertThat(clazz.field(enumTypeName, "VALUE1"), isRenamed());
+    assertThat(clazz.field(enumTypeName, "VALUE2"), isRenamed());
+    assertThat(clazz.field(enumTypeName + "[]", "$VALUES"), isRenamed());
+    assertThat(
+        clazz.method(enumTypeName, "valueOf", ImmutableList.of("java.lang.String")),
+        valueOfKept ? isRenamed() : not(isPresent()));
+    assertThat(clazz.method(enumTypeName + "[]", "values", ImmutableList.of()), not(isRenamed()));
+
+    assertEquals("VALUE1", runOnVM(output, mainClass, backend));
+  }
+
   @Test
   public void test() throws Exception {
-    AndroidApp output =
-        ToolHelper.runR8(
-            R8Command.builder()
-                .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
-                .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
-                .addProguardConfiguration(
-                    ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
-                .setProgramConsumer(emptyConsumer(backend))
-                .build());
+    runTest(Main.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), true);
+  }
 
-    // TODO(117299356): valueOf on enum fails for minified enums.
-    ProcessResult result = runOnVMRaw(output, Main.class, backend);
-    assertEquals(1, result.exitCode);
-    assertThat(
-        result.stderr,
-        containsString(
-            backend == Backend.DEX
-                ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
-                    ? "java.lang.NoSuchMethodException"
-                    : "java.lang.NullPointerException"
-                : "java.lang.IllegalArgumentException"));
+  @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.
+    buildApp(Main.class, EnumDump.dump(false));
+  }
+
+  @Test
+  public void testJavaLangEnumValueOf() throws Exception {
+    runTest(Main2.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), false);
   }
 }
 
@@ -67,7 +103,7 @@
 
   public static void main(String[] args) {
     Enum e = Enum.valueOf("VALUE1");
-    System.out.println(e);
+    System.out.print(e);
   }
 }
 
@@ -75,3 +111,219 @@
   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();
+  }
+}