Mark all proto fields as being read using reflection

Bug: 141599586
Change-Id: I6cf982c2115b01149a1b06b53cd4bc42f7a48bd1
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 2af1aa5..079b8bb 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -28,4 +28,6 @@
   boolean isReadOnlyIn(DexEncodedMethod method);
 
   boolean isWritten();
+
+  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 b6c54cb..fcd4f5c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -146,6 +146,23 @@
     return writesWithContexts != null && !writesWithContexts.isEmpty();
   }
 
+  /**
+   * Returns true if this field is written by a method in the program other than {@param method}.
+   */
+  @Override
+  public boolean isWrittenOutside(DexEncodedMethod method) {
+    if (writesWithContexts != null) {
+      for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) {
+        for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) {
+          if (encodedWriteContext != method) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
   public boolean recordRead(DexField access, DexEncodedMethod context) {
     if (readsWithContexts == null) {
       readsWithContexts = new IdentityHashMap<>();
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 ac6bcde..0ca8fac 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
@@ -144,13 +144,13 @@
 
         boolean encodedValueStorageIsLive;
         if (enqueuer.isFieldLive(encodedValueStorage)) {
-          // Mark that the field is written by reflection such that we do not optimize field reads
-          // into loading the default value of the field.
-          enqueuer.registerFieldWrite(encodedValueStorage.field, dynamicMethod);
-          // 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.
-          if (reachesMapOrRequiredField(protoFieldInfo)) {
-            enqueuer.registerFieldRead(encodedValueStorage.field, dynamicMethod);
+          if (enqueuer.isFieldRead(encodedValueStorage)
+              || enqueuer.isFieldWrittenOutsideDefaultConstructor(encodedValueStorage)) {
+            // Mark that the field is both read and written by reflection such that we do not
+            // (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.registerFieldAccess(encodedValueStorage.field, dynamicMethod);
           }
           encodedValueStorageIsLive = true;
         } else if (reachesMapOrRequiredField(protoFieldInfo)) {
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 1d209ea..0689533 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1786,6 +1786,23 @@
     return liveFields.contains(field);
   }
 
+  public boolean isFieldRead(DexEncodedField field) {
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+    return info != null && info.isRead();
+  }
+
+  public boolean isFieldWrittenOutsideDefaultConstructor(DexEncodedField field) {
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+    if (info == null) {
+      return false;
+    }
+    DexClass clazz = appView.definitionFor(field.field.holder);
+    DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+    return defaultInitializer != null
+        ? info.isWrittenOutside(defaultInitializer)
+        : info.isWritten();
+  }
+
   private boolean isInstantiatedOrHasInstantiatedSubtype(DexProgramClass clazz) {
     return directAndIndirectlyInstantiatedTypes.contains(clazz);
   }