Stop relying on $VALUES name

Bug: 160535629
Change-Id: I379f4c548426b9f10753f7b18612edd77e57926c
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 e6d824a..e2845c4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -292,6 +292,11 @@
       createString(Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX);
 
   public final DexString thisName = createString("this");
+
+  // As much as possible, R8 should rely on the content of the static enum field, using
+  // enumMembers.isValuesFieldCandidate or checking the object state in the optimization info.
+  // The field name is unrealiable since the filed can be minified prior to this compilation.
+  // We keep enumValuesFieldName as a heuristic only.
   public final DexString enumValuesFieldName = createString("$VALUES");
 
   public final DexString enabledFieldName = createString("ENABLED");
@@ -1380,21 +1385,17 @@
       return field == nameField || field == ordinalField;
     }
 
-    public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
-      assert enumClass.isEnum();
-      return method.holder == enumClass.type
-          && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
-          && method.proto.parameters.size() == 0
-          && method.name == valuesMethodName;
+    public boolean isEnumField(DexEncodedField staticField, DexType enumType) {
+      assert staticField.isStatic();
+      return staticField.getType() == enumType && staticField.isEnum() && staticField.isFinal();
     }
 
-    public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
-      assert enumClass.isEnum();
-      return method.holder == enumClass.type
-          && method.proto.returnType == enumClass.type
-          && method.proto.parameters.size() == 1
-          && method.proto.parameters.values[0] == stringType
-          && method.name == valueOfMethodName;
+    public boolean isValuesFieldCandidate(DexEncodedField staticField, DexType enumType) {
+      assert staticField.isStatic();
+      return staticField.getType().isArrayType()
+          && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
+          && staticField.isSynthetic()
+          && staticField.isFinal();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 281215f..fc01602 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,9 +12,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -26,6 +23,7 @@
 import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -40,10 +38,13 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
+import java.util.IdentityHashMap;
+import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
   private final StaticFieldValues.Builder builder;
+  private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
 
   private StaticFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -63,7 +64,7 @@
     timing.begin("Analyze class initializer");
     StaticFieldValues result =
         new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
-            .analyze(classInitializerDefaultsResult, code.context().getHolderType());
+            .analyze(classInitializerDefaultsResult);
     timing.end();
     return result;
   }
@@ -78,8 +79,7 @@
     return this;
   }
 
-  StaticFieldValues analyze(
-      ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
+  StaticFieldValues analyze(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     computeFieldOptimizationInfo(classInitializerDefaultsResult);
     return builder.build();
   }
@@ -218,16 +218,41 @@
       return null;
     }
     assert !value.hasAliasedValue();
-    if (isEnumValuesArray(value)) {
+    if (value.isPhi()) {
+      return null;
+    }
+    if (value.definition.isNewArrayEmpty()) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
-    return computeSingleEnumFieldValueForInstance(value);
+    if (value.definition.isNewInstance()) {
+      return computeSingleEnumFieldValueForInstance(value);
+    }
+    return null;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+    if (!value.definition.isNewArrayEmpty()) {
       return null;
     }
+    AbstractValue valuesValue = computedValues.get(value);
+    if (valuesValue != null) {
+      // This implicitely answers null if the value could not get computed.
+      if (valuesValue.isSingleFieldValue()) {
+        SingleFieldValue fieldValue = valuesValue.asSingleFieldValue();
+        if (fieldValue.getState().isEnumValuesObjectState()) {
+          return fieldValue;
+        }
+      }
+      return null;
+    }
+    SingleFieldValue singleFieldValue = internalComputeSingleEnumFieldValueForValuesArray(value);
+    computedValues.put(
+        value, singleFieldValue == null ? UnknownValue.getInstance() : singleFieldValue);
+    return singleFieldValue;
+  }
+
+  private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
+    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
 
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
     if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
@@ -267,7 +292,9 @@
             // We need the state of all fields for the analysis to be valuable.
             return null;
           }
-          assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
+          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+            return null;
+          }
           if (valuesState[index] != null) {
             return null;
           }
@@ -326,24 +353,25 @@
     return ObjectState.empty();
   }
 
-  private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+  private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
     DexEncodedField ordinalField =
         appView
             .appInfo()
             .resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
             .getResolvedField();
-    assert ordinalField != null;
+    if (ordinalField == null) {
+      return false;
+    }
     AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
-    assert ordinalState != null;
-    assert ordinalState.isSingleNumberValue();
-    assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
-    return true;
+    if (ordinalState == null || !ordinalState.isSingleNumberValue()) {
+      return false;
+    }
+    int intValue = ordinalState.asSingleNumberValue().getIntValue();
+    return intValue == ordinal;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
-      return null;
-    }
+    assert value.isDefinedByInstructionSatisfying(Instruction::isNewInstance);
 
     NewInstance newInstance = value.definition.asNewInstance();
     // Some enums have direct subclasses, and the subclass is instantiated here.
@@ -459,30 +487,7 @@
   }
 
   private boolean isEnumValuesArray(Value value) {
-    assert context.getHolder().isEnum();
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexField valuesField =
-        dexItemFactory.createField(
-            context.getHolderType(),
-            context.getHolderType().toArrayType(1, dexItemFactory),
-            dexItemFactory.enumValuesFieldName);
-
-    Value root = value.getAliasedValue();
-    if (root.isPhi()) {
-      return false;
-    }
-
-    Instruction definition = root.definition;
-    if (definition.isNewArrayEmpty()) {
-      for (Instruction user : root.aliasedUsers()) {
-        if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
-          return true;
-        }
-      }
-    } else if (definition.isStaticGet()) {
-      return definition.asStaticGet().getField() == valuesField;
-    }
-
-    return false;
+    SingleFieldValue singleFieldValue = computeSingleEnumFieldValueForValuesArray(value);
+    return singleFieldValue != null && singleFieldValue.getState().isEnumValuesObjectState();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index b2dcdcd..65e1ea3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -37,17 +37,10 @@
   // All the abstract values stored here may match a pinned field, using them requires therefore
   // to check the field is not pinned or prove it is no longer pinned.
   public static class EnumStaticFieldValues extends StaticFieldValues {
-    private final ImmutableMap<DexField, AbstractValue> enumAbstractValues;
-    private final DexField valuesField;
-    private final AbstractValue valuesAbstractValue;
+    private final ImmutableMap<DexField, ObjectState> enumAbstractValues;
 
-    public EnumStaticFieldValues(
-        ImmutableMap<DexField, AbstractValue> enumAbstractValues,
-        DexField valuesField,
-        AbstractValue valuesAbstractValue) {
+    public EnumStaticFieldValues(ImmutableMap<DexField, ObjectState> enumAbstractValues) {
       this.enumAbstractValues = enumAbstractValues;
-      this.valuesField = valuesField;
-      this.valuesAbstractValue = valuesAbstractValue;
     }
 
     static StaticFieldValues.Builder builder() {
@@ -55,33 +48,38 @@
     }
 
     public static class Builder extends StaticFieldValues.Builder {
-      private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
+      private final ImmutableMap.Builder<DexField, ObjectState> enumObjectStateBuilder =
           ImmutableMap.builder();
-      private DexField valuesFields;
-      private AbstractValue valuesAbstractValue;
+      private AbstractValue valuesCandidateAbstractValue;
 
       Builder() {}
 
       @Override
       public void recordStaticField(
           DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
-        // TODO(b/166532388): Stop relying on the values name.
-        if (staticField.getName() == factory.enumValuesFieldName) {
-          valuesFields = staticField.field;
-          valuesAbstractValue = value;
-        } else if (staticField.isEnum()) {
-          enumAbstractValuesBuilder.put(staticField.field, value);
+        if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
+          if (value.isSingleFieldValue()
+              && value.asSingleFieldValue().getState().isEnumValuesObjectState()) {
+            assert valuesCandidateAbstractValue == null
+                || valuesCandidateAbstractValue.equals(value);
+            valuesCandidateAbstractValue = value;
+            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+          }
+        } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
+          if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
+            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+          }
         }
       }
 
       @Override
       public StaticFieldValues build() {
-        ImmutableMap<DexField, AbstractValue> enumAbstractValues =
-            enumAbstractValuesBuilder.build();
-        if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
+        ImmutableMap<DexField, ObjectState> enumAbstractValues = enumObjectStateBuilder.build();
+        if (enumAbstractValues.isEmpty()) {
           return EmptyStaticValues.getInstance();
         }
-        return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
+        assert enumAbstractValues.values().stream().noneMatch(ObjectState::isEmpty);
+        return new EnumStaticFieldValues(enumAbstractValues);
       }
     }
 
@@ -96,20 +94,7 @@
     }
 
     public ObjectState getObjectStateForPossiblyPinnedField(DexField field) {
-      AbstractValue fieldValue = enumAbstractValues.get(field);
-      if (fieldValue == null || fieldValue.isZero()) {
-        return null;
-      }
-      if (fieldValue.isSingleFieldValue()) {
-        return fieldValue.asSingleFieldValue().getState();
-      }
-      assert fieldValue.isUnknown();
-      return ObjectState.empty();
-    }
-
-    public AbstractValue getValuesAbstractValueForPossiblyPinnedField(DexField field) {
-      assert valuesField == field || valuesAbstractValue == null;
-      return valuesAbstractValue;
+      return enumAbstractValues.get(field);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 166022e..cf0ecb5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -491,34 +491,36 @@
     // Step 1: We iterate over the field to find direct enum instance information and the values
     // fields.
     for (DexEncodedField staticField : enumClass.staticFields()) {
-      if (EnumUnboxingCandidateAnalysis.isEnumField(staticField, enumClass.type)) {
+      if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
         ObjectState enumState =
             enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
-        if (enumState != null) {
-          OptionalInt optionalOrdinal = getOrdinal(enumState);
-          if (!optionalOrdinal.isPresent()) {
-            return null;
+        if (enumState == null) {
+          if (!isFinalFieldInitialized(staticField, enumClass)) {
+            continue;
           }
-          int ordinal = optionalOrdinal.getAsInt();
-          unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
-          ordinalToObjectState.put(ordinal, enumState);
-        }
-      } else if (EnumUnboxingCandidateAnalysis.matchesValuesField(
-          staticField, enumClass.type, factory)) {
-        AbstractValue valuesValue =
-            enumStaticFieldValues.getValuesAbstractValueForPossiblyPinnedField(staticField.field);
-        if (valuesValue == null || valuesValue.isZero()) {
-          // Unused field
-          continue;
-        }
-        if (valuesValue.isUnknown()) {
+          // Tracking the content of the field yielded either an empty object state, or something
+          // incoherent. We bail out.
           return null;
         }
-        assert valuesValue.isSingleFieldValue();
-        ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
-        if (!valuesState.isEnumValuesObjectState()) {
+        OptionalInt optionalOrdinal = getOrdinal(enumState);
+        if (!optionalOrdinal.isPresent()) {
           return null;
         }
+        int ordinal = optionalOrdinal.getAsInt();
+        unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
+        ordinalToObjectState.put(ordinal, enumState);
+      } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
+        ObjectState valuesState =
+            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
+        if (valuesState == null) {
+          if (!isFinalFieldInitialized(staticField, enumClass)) {
+            continue;
+          }
+          // We could not track the content of that field, and the field could be a values field.
+          // We conservatively bail out.
+          return null;
+        }
+        assert valuesState.isEnumValuesObjectState();
         assert valuesContents == null
             || valuesContents.equals(valuesState.asEnumValuesObjectState());
         valuesContents = valuesState.asEnumValuesObjectState();
@@ -564,6 +566,13 @@
         valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
   }
 
+  private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) {
+    assert staticField.isFinal();
+    return appView
+        .appInfo()
+        .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer());
+  }
+
   private EnumInstanceFieldData computeEnumFieldData(
       DexField instanceField,
       DexProgramClass enumClass,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 6c20c4f..4c7581e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -434,7 +434,7 @@
     return factory.createField(
         relocator.getNewMemberLocationFor(enumType),
         factory.intArrayType,
-        factory.enumValuesFieldName + "$field$" + compatibleName(enumType));
+        "$$values$$field$" + compatibleName(enumType));
   }
 
   private DexEncodedField computeValuesEncodedField(DexField field) {
@@ -451,7 +451,7 @@
     return factory.createMethod(
         relocator.getNewMemberLocationFor(enumType),
         factory.createProto(factory.intArrayType),
-        factory.enumValuesFieldName + "$method$" + compatibleName(enumType));
+        "$$values$$method$" + compatibleName(enumType));
   }
 
   private DexEncodedMethod computeValuesEncodedMethod(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index 89395ef..b21cbd0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -97,13 +97,6 @@
     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(