Support static field analysis for enum pinned fields

Bug: 172528424
Change-Id: I8fe2a9739116af9e90759933b5fbcb418f7e5588
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 8e52af1..ee985c4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -86,10 +87,16 @@
     return null;
   }
 
+  boolean isStaticFieldValueAnalysis() {
+    return false;
+  }
+
   StaticFieldValueAnalysis asStaticFieldValueAnalysis() {
     return null;
   }
 
+  abstract boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field);
+
   abstract boolean isSubjectToOptimization(DexEncodedField field);
 
   void recordFieldPut(DexEncodedField field, Instruction instruction) {
@@ -118,9 +125,18 @@
         if (instruction.isFieldPut()) {
           FieldInstruction fieldPut = instruction.asFieldInstruction();
           DexField field = fieldPut.getField();
-          DexEncodedField encodedField = appInfo.resolveField(field).getResolvedField();
-          if (encodedField != null && isSubjectToOptimization(encodedField)) {
-            recordFieldPut(encodedField, fieldPut);
+          SuccessfulFieldResolutionResult fieldResolutionResult =
+              appInfo.resolveField(field).asSuccessfulResolution();
+          if (fieldResolutionResult != null) {
+            DexEncodedField encodedField = fieldResolutionResult.getResolvedField();
+            assert encodedField != null;
+            if (isSubjectToOptimization(encodedField)) {
+              recordFieldPut(encodedField, fieldPut);
+            } else if (isStaticFieldValueAnalysis()
+                && fieldResolutionResult.getResolvedHolder().isEnum()
+                && isSubjectToOptimizationIgnoringPinning(encodedField)) {
+              recordFieldPut(encodedField, fieldPut);
+            }
           }
         } else if (isInstanceFieldValueAnalysis()
             && instruction.isInvokeConstructor(appView.dexItemFactory())) {
@@ -153,15 +169,15 @@
       boolean priorReadsWillReadSameValue =
           !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
       if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
-        if (!isInstanceFieldValueAnalysis()) {
+        // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
+        if (isStaticFieldValueAnalysis()) {
           // At this point the value read in the field can be only the default static value, if read
           // prior to the put, or the value put, if read after the put. We still want to record it
           // because the default static value is typically null/0, so code present after a null/0
           // check can take advantage of the optimization.
           DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
           asStaticFieldValueAnalysis()
-              .updateFieldOptimizationInfoWith2Values(
-                  field, fieldPut, fieldPut.value(), valueBeforePut);
+              .updateFieldOptimizationInfoWith2Values(field, fieldPut.value(), valueBeforePut);
         }
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index cc607ac..c4061ec 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -124,6 +124,11 @@
   }
 
   @Override
+  boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+    throw new Unreachable("Used by static analysis only.");
+  }
+
+  @Override
   void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
     if (fieldNeverWrittenBetweenInstancePutAndMethodExit(field, fieldPut.asInstancePut())) {
       recordInstanceFieldIsInitializedWithValue(field, value);
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 3ab1305..03455fb 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
@@ -14,6 +14,7 @@
 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;
@@ -42,12 +43,15 @@
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
+  private final StaticFieldValues.Builder builder;
+
   private StaticFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
     super(appView, code, feedback);
+    builder = StaticFieldValues.builder(code.context().getHolder());
   }
 
-  public static void run(
+  public static StaticFieldValues run(
       AppView<?> appView,
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
@@ -57,9 +61,16 @@
     assert appView.enableWholeProgramOptimizations();
     assert code.context().getDefinition().isClassInitializer();
     timing.begin("Analyze class initializer");
-    new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
-        .computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    StaticFieldValues result =
+        new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
+            .analyze(classInitializerDefaultsResult, code.context().getHolderType());
     timing.end();
+    return result;
+  }
+
+  @Override
+  boolean isStaticFieldValueAnalysis() {
+    return true;
   }
 
   @Override
@@ -67,6 +78,12 @@
     return this;
   }
 
+  StaticFieldValues analyze(
+      ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
+    computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    return builder.build();
+  }
+
   @Override
   void computeFieldOptimizationInfo(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     super.computeFieldOptimizationInfo(classInitializerDefaultsResult);
@@ -105,14 +122,32 @@
   }
 
   @Override
-  void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
-    // Abstract value.
-    feedback.recordFieldHasAbstractValue(field, appView, getOrComputeAbstractValue(value, field));
-
-    setDynamicType(field, value, false);
+  boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+    return field.isStatic()
+        && field.getHolderType() == context.getHolderType()
+        && appView
+            .appInfo()
+            .isFieldOnlyWrittenInMethodIgnoringPinning(field, context.getDefinition());
   }
 
-  private void setDynamicType(DexEncodedField field, Value value, boolean maybeNull) {
+  @Override
+  void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+    AbstractValue abstractValue = getOrComputeAbstractValue(value, field);
+    updateFieldOptimizationInfo(field, value, abstractValue, false);
+  }
+
+  void updateFieldOptimizationInfo(
+      DexEncodedField field, Value value, AbstractValue abstractValue, boolean maybeNull) {
+    builder.recordStaticField(field, abstractValue, appView.dexItemFactory());
+
+    // We cannot modify FieldOptimizationInfo of pinned fields.
+    if (appView.appInfo().isPinned(field)) {
+      return;
+    }
+
+    // Abstract value.
+    feedback.recordFieldHasAbstractValue(field, appView, abstractValue);
+
     // Dynamic upper bound type.
     TypeElement fieldType =
         TypeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
@@ -137,16 +172,16 @@
   }
 
   public void updateFieldOptimizationInfoWith2Values(
-      DexEncodedField field, FieldInstruction fieldPut, Value valuePut, DexValue valueBeforePut) {
+      DexEncodedField field, Value valuePut, DexValue valueBeforePut) {
     // We are interested in the AbstractValue only if it's null or a value, so we can use the value
     // if the code is protected by a null check.
     if (valueBeforePut != DexValueNull.NULL) {
       return;
     }
-    feedback.recordFieldHasAbstractValue(
-        field, appView, NullOrAbstractValue.create(getOrComputeAbstractValue(valuePut, field)));
 
-    setDynamicType(field, valuePut, true);
+    AbstractValue abstractValue =
+        NullOrAbstractValue.create(getOrComputeAbstractValue(valuePut, field));
+    updateFieldOptimizationInfo(field, valuePut, abstractValue, true);
   }
 
   private AbstractValue getOrComputeAbstractValue(Value value, DexEncodedField field) {
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
new file mode 100644
index 0000000..8a02e12
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -0,0 +1,162 @@
+// 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.ir.analysis.fieldvalueanalysis;
+
+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.DexProgramClass;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.google.common.collect.ImmutableMap;
+
+public abstract class StaticFieldValues {
+
+  public boolean isEnumStaticFieldValues() {
+    return false;
+  }
+
+  public EnumStaticFieldValues asEnumStaticFieldValues() {
+    return null;
+  }
+
+  public static Builder builder(DexProgramClass clazz) {
+    return clazz.isEnum() ? EnumStaticFieldValues.builder() : EmptyStaticValues.builder();
+  }
+
+  public abstract static class Builder {
+
+    public abstract void recordStaticField(
+        DexEncodedField staticField, AbstractValue value, DexItemFactory factory);
+
+    public abstract StaticFieldValues build();
+  }
+
+  // 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;
+
+    public EnumStaticFieldValues(
+        ImmutableMap<DexField, AbstractValue> enumAbstractValues,
+        DexField valuesField,
+        AbstractValue valuesAbstractValue) {
+      this.enumAbstractValues = enumAbstractValues;
+      this.valuesField = valuesField;
+      this.valuesAbstractValue = valuesAbstractValue;
+    }
+
+    static StaticFieldValues.Builder builder() {
+      return new Builder();
+    }
+
+    public static class Builder extends StaticFieldValues.Builder {
+      private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
+          ImmutableMap.builder();
+      private DexField valuesFields;
+      private AbstractValue valuesAbstractValue;
+
+      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);
+        }
+      }
+
+      @Override
+      public StaticFieldValues build() {
+        ImmutableMap<DexField, AbstractValue> enumAbstractValues =
+            enumAbstractValuesBuilder.build();
+        if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
+          return EmptyStaticValues.getInstance();
+        }
+        return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
+      }
+    }
+
+    @Override
+    public boolean isEnumStaticFieldValues() {
+      return true;
+    }
+
+    @Override
+    public EnumStaticFieldValues asEnumStaticFieldValues() {
+      return this;
+    }
+
+    // We need to access the enum instance object state to figure out if it contains known constant
+    // field values. The enum instance may be accessed in two ways, directly through the enum
+    // static field, or through the enum $VALUES field. If none of them are kept, the instance is
+    // effectively unused. The object state may be stored in the enum static field optimization
+    // info, if kept, or in the $VALUES optimization info, if kept.
+    // If the enum instance is unused, this method answers null.
+    public ObjectState getObjectStateForPossiblyPinnedEnumInstance(DexField field, int ordinal) {
+
+      // Attempt 1: Get object state from the instance field's optimization info.
+      AbstractValue fieldValue = enumAbstractValues.get(field);
+      if (fieldValue != null) {
+        if (fieldValue.isSingleFieldValue()) {
+          return fieldValue.asSingleFieldValue().getState();
+        }
+        if (fieldValue.isUnknown()) {
+          return ObjectState.empty();
+        }
+        assert fieldValue.isZero();
+      }
+
+      // Attempt 2: Get object state from the values field's optimization info.
+      if (valuesAbstractValue.isZero()) {
+        // Unused enum instance.
+        return null;
+      }
+      if (valuesAbstractValue.isUnknown()) {
+        return ObjectState.empty();
+      }
+      assert valuesAbstractValue.isSingleFieldValue();
+      ObjectState valuesState = this.valuesAbstractValue.asSingleFieldValue().getState();
+      if (valuesState.isEnumValuesObjectState()) {
+        return valuesState.asEnumValuesObjectState().getObjectStateForOrdinal(ordinal);
+      }
+      return ObjectState.empty();
+    }
+  }
+
+  public static class EmptyStaticValues extends StaticFieldValues {
+    private static EmptyStaticValues INSTANCE = new EmptyStaticValues();
+
+    private EmptyStaticValues() {}
+
+    public static EmptyStaticValues getInstance() {
+      return INSTANCE;
+    }
+
+    static StaticFieldValues.Builder builder() {
+      return new Builder();
+    }
+
+    public static class Builder extends StaticFieldValues.Builder {
+
+      @Override
+      public void recordStaticField(
+          DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
+        // Do nothing.
+      }
+
+      @Override
+      public StaticFieldValues build() {
+        return EmptyStaticValues.getInstance();
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2ef58cd..e340861 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
 import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
@@ -1708,16 +1709,21 @@
     }
 
     InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos = null;
+    StaticFieldValues staticFieldValues = null;
     if (method.getDefinition().isInitializer()) {
       if (method.getDefinition().isClassInitializer()) {
-        StaticFieldValueAnalysis.run(
-            appView, code, classInitializerDefaultsResult, feedback, timing);
+        staticFieldValues =
+            StaticFieldValueAnalysis.run(
+                appView, code, classInitializerDefaultsResult, feedback, timing);
       } else {
         instanceFieldInitializationInfos =
             InstanceFieldValueAnalysis.run(
                 appView, code, classInitializerDefaultsResult, feedback, timing);
       }
     }
+    if (enumUnboxer != null) {
+      enumUnboxer.recordEnumState(method.getHolder(), staticFieldValues);
+    }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
         method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos, timing);
   }
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 2d46582..2d436ad 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
@@ -28,6 +28,8 @@
 import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
+import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -86,6 +88,8 @@
   private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
   private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
       ProgramPackageCollection.createEmpty();
+  private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
+      new ConcurrentHashMap<>();
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
 
@@ -453,6 +457,7 @@
           }
           builder.put(enumClass.type, typeBuilder.build());
         });
+    staticFieldValuesMap.clear();
     return new EnumInstanceFieldDataMap(builder.build());
   }
 
@@ -494,6 +499,17 @@
     return useRegistry.computeConstraint(method.asProgramMethod(appView));
   }
 
+  public void recordEnumState(DexProgramClass clazz, StaticFieldValues staticFieldValues) {
+    if (staticFieldValues == null || !staticFieldValues.isEnumStaticFieldValues()) {
+      return;
+    }
+    assert clazz.isEnum();
+    EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues();
+    if (getEnumUnboxingCandidateOrNull(clazz.type) != null) {
+      staticFieldValuesMap.put(clazz.type, enumStaticFieldValues);
+    }
+  }
+
   private class EnumAccessibilityUseRegistry extends UseRegistry {
 
     private ProgramMethod context;
@@ -939,8 +955,13 @@
     EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap =
         appView.appInfo().getEnumValueInfoMap(enumClass.type);
     for (DexField staticField : enumValueInfoMap.enumValues()) {
+      EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
+      if (enumStaticFieldValues == null) {
+        return EnumInstanceFieldUnknownData.getInstance();
+      }
       ObjectState enumInstanceState =
-          computeEnumInstanceObjectState(enumClass, staticField, enumValueInfoMap);
+          enumStaticFieldValues.getObjectStateForPossiblyPinnedEnumInstance(
+              staticField, enumValueInfoMap.getEnumValueInfo(staticField).ordinal);
       if (enumInstanceState == null) {
         // The enum instance is effectively unused. No need to generate anything for it, the path
         // will never be taken.
@@ -966,52 +987,6 @@
     return new EnumInstanceFieldMappingData(data);
   }
 
-  // We need to access the enum instance object state to figure out if it contains known constant
-  // field values. The enum instance may be accessed in two ways, directly through the enum
-  // static field, or through the enum $VALUES field. If none of them are kept, the instance is
-  // effectively unused. The object state may be stored in the enum static field optimization
-  // info, if kept, or in the $VALUES optimization info, if kept.
-  // If the enum instance is unused, this method answers null.
-  private ObjectState computeEnumInstanceObjectState(
-      DexProgramClass enumClass,
-      DexField staticField,
-      EnumValueInfoMapCollection.EnumValueInfoMap enumValueInfoMap) {
-    // Attempt 1: Get object state from the instance field's optimization info.
-    DexEncodedField encodedStaticField = enumClass.lookupStaticField(staticField);
-    AbstractValue enumInstanceValue = encodedStaticField.getOptimizationInfo().getAbstractValue();
-    if (enumInstanceValue.isSingleFieldValue()) {
-      return enumInstanceValue.asSingleFieldValue().getState();
-    }
-    if (enumInstanceValue.isUnknown()) {
-      return ObjectState.empty();
-    }
-    assert enumInstanceValue.isZero();
-
-    // Attempt 2: Get object state from the values field's optimization info.
-    DexEncodedField valuesField =
-        enumClass.lookupStaticField(
-            factory.createField(
-                enumClass.type,
-                factory.createArrayType(1, enumClass.type),
-                factory.enumValuesFieldName));
-    AbstractValue valuesValue = valuesField.getOptimizationInfo().getAbstractValue();
-    if (valuesValue.isZero()) {
-      // Unused enum instance.
-      return null;
-    }
-    if (valuesValue.isUnknown()) {
-      return ObjectState.empty();
-    }
-    assert valuesValue.isSingleFieldValue();
-    ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
-    if (valuesState.isEnumValuesObjectState()) {
-      return valuesState
-          .asEnumValuesObjectState()
-          .getObjectStateForOrdinal(enumValueInfoMap.getEnumValueInfo(staticField).ordinal);
-    }
-    return ObjectState.empty();
-  }
-
   private void reportEnumsAnalysis() {
     assert debugLogEnabled;
     Reporter reporter = appView.options().reporter;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 7687ad4..c5adef8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -803,6 +803,13 @@
     if (isPinned(field.field)) {
       return false;
     }
+    return isFieldOnlyWrittenInMethodIgnoringPinning(field, method);
+  }
+
+  public boolean isFieldOnlyWrittenInMethodIgnoringPinning(
+      DexEncodedField field, DexEncodedMethod method) {
+    assert checkIfObsolete();
+    assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.field);
     return fieldAccessInfo != null
         && fieldAccessInfo.isWritten()