Account for deferred tracing in proto enqueuer extension

Bug: 205810841
Change-Id: Ic9bd167c73c36dc02113e539e09b47e619d3f2ce
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 9584e69..1c8ba99 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -406,6 +406,11 @@
           GenericSignatureCorrectnessHelper.createForInitialCheck(appView, genericContextBuilder)
               .run(appView.appInfo().classes());
 
+          // TODO(b/226539525): Implement enum lite proto shrinking as deferred tracing.
+          if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
+            appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps();
+          }
+
           TreePruner pruner = new TreePruner(appViewWithLiveness);
           DirectMappedDexApplication prunedApp = pruner.run(executorService);
 
@@ -422,10 +427,6 @@
                   appViewWithLiveness, appViewWithLiveness.appInfo().computeSubtypingInfo())
               .run();
 
-          if (appView.options().protoShrinking().isEnumLiteProtoShrinkingEnabled()) {
-            appView.protoShrinker().enumLiteProtoShrinker.clearDeadEnumLiteMaps();
-          }
-
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .build(appViewWithLiveness, removedClasses);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index c5e19b9..34b3f2b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -42,6 +42,10 @@
 
   boolean hasReflectiveAccess();
 
+  boolean hasReflectiveRead();
+
+  boolean hasReflectiveWrite();
+
   default boolean isAccessedFromMethodHandle() {
     return isReadFromMethodHandle() || isWrittenFromMethodHandle();
   }
@@ -54,6 +58,8 @@
 
   boolean isReadFromMethodHandle();
 
+  boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
+
   boolean isWritten();
 
   boolean isWrittenFromMethodHandle();
@@ -62,7 +68,5 @@
 
   boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
 
-  boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
-
   boolean isWrittenOutside(DexEncodedMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index bdec9c0..8779817 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -27,8 +27,9 @@
   public static int FLAG_IS_READ_FROM_ANNOTATION = 1 << 0;
   public static int FLAG_IS_READ_FROM_METHOD_HANDLE = 1 << 1;
   public static int FLAG_IS_WRITTEN_FROM_METHOD_HANDLE = 1 << 2;
-  public static int FLAG_HAS_REFLECTIVE_ACCESS = 1 << 3;
-  public static int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 4;
+  public static int FLAG_HAS_REFLECTIVE_READ = 1 << 3;
+  public static int FLAG_HAS_REFLECTIVE_WRITE = 1 << 4;
+  public static int FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC = 1 << 5;
 
   // A direct reference to the definition of the field.
   private DexField field;
@@ -197,17 +198,39 @@
 
   @Override
   public boolean hasReflectiveAccess() {
-    return (flags & FLAG_HAS_REFLECTIVE_ACCESS) != 0;
+    return hasReflectiveRead() || hasReflectiveWrite();
   }
 
-  public void setHasReflectiveAccess() {
-    flags |= FLAG_HAS_REFLECTIVE_ACCESS;
+  @Override
+  public boolean hasReflectiveRead() {
+    return (flags & FLAG_HAS_REFLECTIVE_READ) != 0;
+  }
+
+  public void setHasReflectiveRead() {
+    flags |= FLAG_HAS_REFLECTIVE_READ;
+  }
+
+  @Override
+  public boolean hasReflectiveWrite() {
+    return (flags & FLAG_HAS_REFLECTIVE_WRITE) != 0;
+  }
+
+  public void setHasReflectiveWrite() {
+    flags |= FLAG_HAS_REFLECTIVE_WRITE;
   }
 
   /** Returns true if this field is read by the program. */
   @Override
   public boolean isRead() {
-    return !readsWithContexts.isEmpty()
+    return isReadDirectly() || isReadIndirectly();
+  }
+
+  private boolean isReadDirectly() {
+    return !readsWithContexts.isEmpty();
+  }
+
+  private boolean isReadIndirectly() {
+    return hasReflectiveRead()
         || isReadFromAnnotation()
         || isReadFromMethodHandle()
         || isReadFromRecordInvokeDynamic();
@@ -227,15 +250,15 @@
     return (flags & FLAG_IS_READ_FROM_METHOD_HANDLE) != 0;
   }
 
+  public void setReadFromMethodHandle() {
+    flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
+  }
+
   @Override
   public boolean isReadFromRecordInvokeDynamic() {
     return (flags & FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC) != 0;
   }
 
-  public void setReadFromMethodHandle() {
-    flags |= FLAG_IS_READ_FROM_METHOD_HANDLE;
-  }
-
   public void setReadFromRecordInvokeDynamic() {
     flags |= FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
   }
@@ -244,12 +267,28 @@
     flags &= ~FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
   }
 
+  /**
+   * Returns true if this field is only read by methods for which {@param predicate} returns true.
+   */
+  @Override
+  public boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
+    return readsWithContexts.isAccessedOnlyInMethodSatisfying(predicate) && !isReadIndirectly();
+  }
+
   /** Returns true if this field is written by the program. */
   @Override
   public boolean isWritten() {
+    return isWrittenDirectly() || isWrittenIndirectly();
+  }
+
+  private boolean isWrittenDirectly() {
     return !writesWithContexts.isEmpty();
   }
 
+  private boolean isWrittenIndirectly() {
+    return hasReflectiveWrite() || isWrittenFromMethodHandle();
+  }
+
   @Override
   public boolean isWrittenFromMethodHandle() {
     return (flags & FLAG_IS_WRITTEN_FROM_METHOD_HANDLE) != 0;
@@ -273,15 +312,7 @@
    */
   @Override
   public boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    return writesWithContexts.isAccessedOnlyInMethodSatisfying(predicate);
-  }
-
-  /**
-   * Returns true if this field is only read by methods for which {@param predicate} returns true.
-   */
-  @Override
-  public boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    return readsWithContexts.isAccessedOnlyInMethodSatisfying(predicate);
+    return writesWithContexts.isAccessedOnlyInMethodSatisfying(predicate) && !isWrittenIndirectly();
   }
 
   /**
@@ -289,7 +320,7 @@
    */
   @Override
   public boolean isWrittenOutside(DexEncodedMethod method) {
-    return writesWithContexts.isAccessedOutside(method);
+    return writesWithContexts.isAccessedOutside(method) || isWrittenIndirectly();
   }
 
   public boolean recordRead(DexField access, ProgramMethod context) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
index 6accb73..e527832 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
@@ -86,9 +86,14 @@
       if (enumLite != null) {
         DexEncodedField field =
             enumLite.lookupField(createInternalValueMapField(enumLite.getType()));
-        return field != null
-            && appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)
-            && !appView.appInfo().isFieldRead(field);
+        if (field == null) {
+          return false;
+        }
+        if (appView.appInfo().isFieldRead(field)) {
+          return false;
+        }
+        return !appView.appInfo().isFieldWritten(field)
+            || appView.appInfo().isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field);
       }
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 88d1c47..66d04da 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
@@ -231,10 +232,18 @@
 
   public boolean isDeadProtoExtensionField(DexField fieldReference) {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    ProgramField field = appInfo.resolveField(fieldReference).getSingleProgramField();
-    return field != null
-        && isDeadProtoExtensionField(
-            field, appInfo.getFieldAccessInfoCollection(), appInfo.getKeepInfo());
+    return isDeadProtoExtensionField(
+        appInfo.resolveField(fieldReference),
+        appInfo.getFieldAccessInfoCollection(),
+        appInfo.getKeepInfo());
+  }
+
+  public boolean isDeadProtoExtensionField(
+      FieldResolutionResult resolutionResult,
+      FieldAccessInfoCollection<?> fieldAccessInfoCollection,
+      KeepInfoCollection keepInfo) {
+    ProgramField field = resolutionResult.getSingleProgramField();
+    return field != null && isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo);
   }
 
   public boolean isDeadProtoExtensionField(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index b3976c7..e84fc5a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -384,7 +384,7 @@
         }
 
         boolean valueStorageIsLive;
-        if (enqueuer.isFieldLive(valueStorage)) {
+        if (enqueuer.isFieldReferenced(valueStorage)) {
           if (enqueuer.isFieldRead(valueStorage)
               || enqueuer.isFieldWrittenOutsideDefaultConstructor(valueStorage)
               || reachesMapOrRequiredField(protoFieldInfo)) {
@@ -392,15 +392,13 @@
             // (i) optimize field reads into loading the default value of the field or (ii) remove
             // field writes to proto fields that could be read using reflection by the proto
             // library.
-            enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
+            worklist.enqueueTraceReflectiveFieldAccessAction(valueStorage, dynamicMethod);
           }
           valueStorageIsLive = true;
         } else if (reachesMapOrRequiredField(protoFieldInfo)) {
           // Map/required fields cannot be removed. Therefore, we mark such fields as both read and
           // written such that we cannot optimize any field reads or writes.
-          enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
-          worklist.enqueueMarkFieldAsReachableAction(
-              valueStorage, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
+          worklist.enqueueTraceReflectiveFieldAccessAction(valueStorage, dynamicMethod);
           valueStorageIsLive = true;
         } else {
           valueStorageIsLive = false;
@@ -414,7 +412,7 @@
             newlyLiveField = protoFieldInfo.getOneOfCaseField(appView, protoMessageInfo);
           } else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
             newlyLiveField = protoFieldInfo.getHazzerBitField(appView, protoMessageInfo);
-            enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
+            worklist.enqueueTraceReflectiveFieldAccessAction(valueStorage, dynamicMethod);
           }
         } else {
           // For one-of fields, mark the one-of field as live if the one-of-case field is live, and
@@ -423,13 +421,13 @@
           if (protoFieldInfo.getType().isOneOf()) {
             ProgramField oneOfCaseField =
                 protoFieldInfo.getOneOfCaseField(appView, protoMessageInfo);
-            if (oneOfCaseField != null && enqueuer.isFieldLive(oneOfCaseField)) {
+            if (oneOfCaseField != null && enqueuer.isFieldReferenced(oneOfCaseField)) {
               newlyLiveField = valueStorage;
             }
           } else if (protoFieldInfo.hasHazzerBitField(protoMessageInfo)) {
             ProgramField hazzerBitField =
                 protoFieldInfo.getHazzerBitField(appView, protoMessageInfo);
-            if (hazzerBitField == null || !enqueuer.isFieldLive(hazzerBitField)) {
+            if (hazzerBitField == null || !enqueuer.isFieldReferenced(hazzerBitField)) {
               continue;
             }
 
@@ -458,15 +456,12 @@
                       && !writer.isStructurallyEqualTo(dynamicMethod);
           if (enqueuer.isFieldWrittenInMethodSatisfying(
               newlyLiveField, neitherDefaultConstructorNorDynamicMethod)) {
-            enqueuer.registerReflectiveFieldRead(newlyLiveField.getReference(), dynamicMethod);
+            worklist.enqueueTraceReflectiveFieldReadAction(newlyLiveField, dynamicMethod);
           }
 
           // Unconditionally register the hazzer and one-of proto fields as written from
           // dynamicMethod().
-          if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.getReference(), dynamicMethod)) {
-            worklist.enqueueMarkFieldAsReachableAction(
-                newlyLiveField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
-          }
+          worklist.enqueueTraceReflectiveFieldWriteAction(newlyLiveField, dynamicMethod);
         }
       }
 
@@ -497,7 +492,7 @@
         // schema, and therefore we do need to trace the const-class instructions that will be
         // emitted for it.
         ProgramField valueStorage = protoFieldInfo.getValueStorage(appView, protoMessageInfo);
-        if (valueStorage != null && enqueuer.isFieldLive(valueStorage)) {
+        if (valueStorage != null && enqueuer.isFieldReferenced(valueStorage)) {
           for (ProtoObject object : objects) {
             if (object.isProtoObjectFromStaticGet()) {
               worklist.enqueueTraceStaticFieldRead(
@@ -554,7 +549,7 @@
       return;
     }
 
-    if (!enqueuer.isFieldLive(oneOfCaseField)) {
+    if (!enqueuer.isFieldReferenced(oneOfCaseField)) {
       return;
     }
 
@@ -578,10 +573,7 @@
       return;
     }
 
-    if (enqueuer.registerReflectiveFieldWrite(oneOfField.getReference(), dynamicMethod)) {
-      worklist.enqueueMarkFieldAsReachableAction(
-          oneOfField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
-    }
+    worklist.enqueueTraceReflectiveFieldWriteAction(oneOfField, dynamicMethod);
   }
 
   /**
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 ee61cc7..c7917ce 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -974,22 +974,39 @@
     return registerFieldAccess(field, context, true, false);
   }
 
-  public boolean registerReflectiveFieldRead(DexField field, ProgramMethod context) {
-    return registerFieldAccess(field, context, true, true);
+  public boolean registerReflectiveFieldRead(ProgramField field, ProgramMethod context) {
+    return registerFieldAccess(field.getReference(), context, true, true);
   }
 
   public boolean registerFieldWrite(DexField field, ProgramMethod context) {
     return registerFieldAccess(field, context, false, false);
   }
 
-  public boolean registerReflectiveFieldWrite(DexField field, ProgramMethod context) {
-    return registerFieldAccess(field, context, false, true);
+  public boolean registerReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
+    return registerFieldAccess(field.getReference(), context, false, true);
   }
 
-  public boolean registerReflectiveFieldAccess(DexField field, ProgramMethod context) {
-    boolean changed = registerFieldAccess(field, context, true, true);
-    changed |= registerFieldAccess(field, context, false, true);
-    return changed;
+  public void traceReflectiveFieldAccess(ProgramField field, ProgramMethod context) {
+    deferredTracing.notifyReflectiveFieldAccess(field, context);
+    boolean changed = registerReflectiveFieldRead(field, context);
+    changed |= registerReflectiveFieldWrite(field, context);
+    if (changed) {
+      markFieldAsReachable(field, context, KeepReason.reflectiveUseIn(context));
+    }
+  }
+
+  public void traceReflectiveFieldRead(ProgramField field, ProgramMethod context) {
+    deferredTracing.notifyReflectiveFieldAccess(field, context);
+    if (registerReflectiveFieldRead(field, context)) {
+      markFieldAsReachable(field, context, KeepReason.reflectiveUseIn(context));
+    }
+  }
+
+  public void traceReflectiveFieldWrite(ProgramField field, ProgramMethod context) {
+    deferredTracing.notifyReflectiveFieldAccess(field, context);
+    if (registerReflectiveFieldWrite(field, context)) {
+      markFieldAsReachable(field, context, KeepReason.reflectiveUseIn(context));
+    }
   }
 
   private boolean registerFieldAccess(
@@ -1023,7 +1040,18 @@
       return false;
     }
     if (isReflective) {
-      info.setHasReflectiveAccess();
+      if (isRead) {
+        if (!info.hasReflectiveRead()) {
+          info.setHasReflectiveRead();
+          return true;
+        }
+      } else {
+        if (!info.hasReflectiveWrite()) {
+          info.setHasReflectiveWrite();
+          return true;
+        }
+      }
+      return false;
     }
     return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
   }
@@ -1718,6 +1746,21 @@
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+
+    if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
+      // If it is a dead proto extension field, don't trace onwards.
+      boolean skipTracing =
+          appView.withGeneratedExtensionRegistryShrinker(
+              shrinker ->
+                  shrinker.isDeadProtoExtensionField(
+                      resolutionResult, fieldAccessInfoCollection, keepInfo),
+              false);
+      if (skipTracing) {
+        addDeadProtoTypeCandidate(resolutionResult.getSingleProgramField().getHolder());
+        return;
+      }
+    }
+
     if (deferredTracing.deferTracingOfFieldAccess(
         fieldReference, resolutionResult, currentMethod, FieldAccessKind.STATIC_READ, metadata)) {
       assert !metadata.isDeferred();
@@ -1751,18 +1794,6 @@
             Log.verbose(getClass(), "Register Sget `%s`.", fieldReference);
           }
 
-          // If it is a dead proto extension field, don't trace onwards.
-          boolean skipTracing =
-              appView.withGeneratedExtensionRegistryShrinker(
-                  shrinker ->
-                      shrinker.isDeadProtoExtensionField(
-                          field, fieldAccessInfoCollection, keepInfo),
-                  false);
-          if (skipTracing) {
-            addDeadProtoTypeCandidate(field.getHolder());
-            return;
-          }
-
           if (field.getReference() != fieldReference) {
             // Mark the initial resolution holder as live. Note that this should only be done if
             // the field
@@ -1799,6 +1830,21 @@
     }
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+
+    if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
+      // If it is a dead proto extension field, don't trace onwards.
+      boolean skipTracing =
+          appView.withGeneratedExtensionRegistryShrinker(
+              shrinker ->
+                  shrinker.isDeadProtoExtensionField(
+                      resolutionResult, fieldAccessInfoCollection, keepInfo),
+              false);
+      if (skipTracing) {
+        addDeadProtoTypeCandidate(resolutionResult.getSingleProgramField().getHolder());
+        return;
+      }
+    }
+
     if (deferredTracing.deferTracingOfFieldAccess(
         fieldReference, resolutionResult, currentMethod, FieldAccessKind.STATIC_WRITE, metadata)) {
       assert !metadata.isDeferred();
@@ -1832,20 +1878,6 @@
             Log.verbose(getClass(), "Register Sput `%s`.", fieldReference);
           }
 
-          if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
-            // If it is a dead proto extension field, don't trace onwards.
-            boolean skipTracing =
-                appView.withGeneratedExtensionRegistryShrinker(
-                    shrinker ->
-                        shrinker.isDeadProtoExtensionField(
-                            field, fieldAccessInfoCollection, keepInfo),
-                    false);
-            if (skipTracing) {
-              addDeadProtoTypeCandidate(field.getHolder());
-              return;
-            }
-          }
-
           if (field.getReference() != fieldReference) {
             // Mark the initial resolution holder as live. Note that this should only be done if
             // the field
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
index 2867c62..0c5905a 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -143,6 +143,10 @@
     return true;
   }
 
+  public void notifyReflectiveFieldAccess(ProgramField field, ProgramMethod context) {
+    enqueueDeferredEnqueuerActions(field);
+  }
+
   private boolean isEligibleForPruning(ProgramField field) {
     FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
     if (info.hasReflectiveAccess()
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 1a5b588..a09efe6 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -11,9 +11,11 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
 import com.android.tools.r8.utils.Action;
@@ -318,6 +320,36 @@
     }
   }
 
+  static class TraceReflectiveFieldAccessAction extends EnqueuerAction {
+    private final ProgramField field;
+    private final ProgramMethod context;
+    private final FieldAccessKind kind;
+
+    TraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
+      this(field, context, null);
+    }
+
+    TraceReflectiveFieldAccessAction(
+        ProgramField field, ProgramMethod context, FieldAccessKind kind) {
+      this.field = field;
+      this.context = context;
+      this.kind = kind;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      if (kind != null) {
+        if (kind.isRead()) {
+          enqueuer.traceReflectiveFieldRead(field, context);
+        } else {
+          enqueuer.traceReflectiveFieldWrite(field, context);
+        }
+      } else {
+        enqueuer.traceReflectiveFieldAccess(field, context);
+      }
+    }
+  }
+
   static class TraceTypeReferenceAction extends EnqueuerAction {
     private final DexProgramClass clazz;
     private final KeepReason reason;
@@ -559,6 +591,15 @@
 
   public abstract void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context);
 
+  public abstract void enqueueTraceReflectiveFieldAccessAction(
+      ProgramField field, ProgramMethod context);
+
+  public abstract void enqueueTraceReflectiveFieldReadAction(
+      ProgramField field, ProgramMethod context);
+
+  public abstract void enqueueTraceReflectiveFieldWriteAction(
+      ProgramField field, ProgramMethod context);
+
   public abstract void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context);
 
   public abstract void enqueueTraceTypeReferenceAction(DexProgramClass clazz, KeepReason reason);
@@ -693,6 +734,42 @@
     }
 
     @Override
+    public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
+      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      if (info == null || !info.hasReflectiveAccess()) {
+        queue.add(new TraceReflectiveFieldAccessAction(field, context));
+      }
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldReadAction(ProgramField field, ProgramMethod context) {
+      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      if (info == null || !info.hasReflectiveRead()) {
+        queue.add(
+            new TraceReflectiveFieldAccessAction(
+                field,
+                context,
+                field.getAccessFlags().isStatic()
+                    ? FieldAccessKind.STATIC_READ
+                    : FieldAccessKind.INSTANCE_READ));
+      }
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldWriteAction(ProgramField field, ProgramMethod context) {
+      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      if (info == null || !info.hasReflectiveWrite()) {
+        queue.add(
+            new TraceReflectiveFieldAccessAction(
+                field,
+                context,
+                field.getAccessFlags().isStatic()
+                    ? FieldAccessKind.STATIC_WRITE
+                    : FieldAccessKind.INSTANCE_WRITE));
+      }
+    }
+
+    @Override
     public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
       queue.add(new TraceStaticFieldReadAction(field, context, FieldAccessMetadata.DEFAULT));
     }
@@ -830,6 +907,21 @@
     }
 
     @Override
+    public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldReadAction(ProgramField field, ProgramMethod context) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
+    public void enqueueTraceReflectiveFieldWriteAction(ProgramField field, ProgramMethod context) {
+      throw attemptToEnqueue();
+    }
+
+    @Override
     public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
       throw attemptToEnqueue();
     }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index da2236d..51acd65 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -73,7 +73,6 @@
     this.parameters = parameters;
   }
 
-  @Ignore
   @Test
   public void test() throws Exception {
     CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
@@ -353,7 +352,6 @@
     }
   }
 
-  @Ignore
   @Test
   public void testNoRewriting() throws Exception {
     testForR8(parameters.getBackend())
@@ -380,7 +378,6 @@
                 assertRewrittenProtoSchemasMatch(new CodeInspector(PROGRAM_FILES), inspector));
   }
 
-  @Ignore
   @Test
   public void testTwoExtensionRegistrys() throws Exception {
     CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index d53d170..e2b8e51 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,7 +51,6 @@
     this.parameters = parameters;
   }
 
-  @Ignore
   @Test
   public void test() throws Exception {
     CodeInspector inputInspector = new CodeInspector(PROGRAM_FILES);
@@ -99,7 +97,6 @@
     }
   }
 
-  @Ignore
   @Test
   public void testNoRewriting() throws Exception {
     testForR8(parameters.getBackend())