Mark hazzer and one-of proto fields as read from dynamicMethod() if they are written in the app

Bug: 144142878
Change-Id: I533dff931e1b47a0a6b8bd2ab3fe90e9ad41c603
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 079b8bb..f573b76 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -7,6 +7,7 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /** Provides immutable access to {@link FieldAccessInfoImpl}. */
 public interface FieldAccessInfo {
@@ -29,5 +30,7 @@
 
   boolean isWritten();
 
+  boolean isWrittenInMethodSatisfying(Predicate<DexEncodedMethod> 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 a263cb6..5924435 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -147,6 +147,23 @@
   }
 
   /**
+   * Returns true if this field is written by a method for which {@param predicate} returns true.
+   */
+  @Override
+  public boolean isWrittenInMethodSatisfying(Predicate<DexEncodedMethod> predicate) {
+    if (writesWithContexts != null) {
+      for (Set<DexEncodedMethod> encodedWriteContexts : writesWithContexts.values()) {
+        for (DexEncodedMethod encodedWriteContext : encodedWriteContexts) {
+          if (predicate.test(encodedWriteContext)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
    * Returns true if this field is written by a method in the program other than {@param method}.
    */
   @Override
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 fce7b09..92954e4 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
@@ -30,6 +30,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 // TODO(b/112437944): Handle cycles in the graph + add a test that fails with the current
 //  implementation. The current caching mechanism is unsafe, because we may mark a message as not
@@ -222,6 +223,19 @@
         }
 
         if (newlyLiveField != null) {
+          // Mark hazzer and one-of proto fields as read from dynamicMethod() if they are written in
+          // the app. This is needed to ensure that field writes are not removed from the app.
+          DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+          assert defaultInitializer != null;
+          Predicate<DexEncodedMethod> neitherDefaultConstructorNorDynamicMethod =
+              writer -> writer != defaultInitializer && writer != dynamicMethod;
+          if (enqueuer.isFieldWrittenInMethodSatisfying(
+              newlyLiveField, neitherDefaultConstructorNorDynamicMethod)) {
+            enqueuer.registerFieldRead(newlyLiveField.field, dynamicMethod);
+          }
+
+          // Unconditionally register the hazzer and one-of proto fields as written from
+          // dynamicMethod().
           if (enqueuer.registerFieldWrite(newlyLiveField.field, dynamicMethod)) {
             worklist.enqueueMarkReachableFieldAction(
                 clazz, newlyLiveField, KeepReason.reflectiveUseIn(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 245f3c6..a9f60ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1907,6 +1907,12 @@
     return info != null && info.isRead();
   }
 
+  public boolean isFieldWrittenInMethodSatisfying(
+      DexEncodedField field, Predicate<DexEncodedMethod> predicate) {
+    FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
+    return info != null && info.isWrittenInMethodSatisfying(predicate);
+  }
+
   public boolean isFieldWrittenOutsideDefaultConstructor(DexEncodedField field) {
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.field);
     if (info == null) {