Change isRead/isWritten to take DexEncodedField

This implicitly guarantees that no indirect field accesses are being passed to isRead() and isWritten().

Change-Id: If3a9c13414ce67de859ef22df94b756d31ab40c7
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 7da40d0..7b57fa2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -147,8 +147,7 @@
     // The only way to figure out whether the DexValue contains the final value
     // is ensure the value is not the default or check <clinit> is not present.
     boolean isEffectivelyFinal =
-        (accessFlags.isFinal() || !appInfo.isFieldWritten(field))
-            && !appInfo.isPinned(field);
+        (accessFlags.isFinal() || !appInfo.isFieldWritten(this)) && !appInfo.isPinned(field);
     if (!isEffectivelyFinal) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index fca35ba..ee563bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -103,13 +103,9 @@
         return true;
       }
 
-      DexEncodedField resolveField = appInfoWithLiveness.resolveField(getField());
-      assert resolveField != null : "NoSuchFieldError (resolution failure) should be caught.";
-      if (appInfoWithLiveness.isFieldRead(resolveField.field)) {
-        return true;
-      }
-
-      return false;
+      DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
+      assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
+      return appInfoWithLiveness.isFieldRead(encodedField);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index a609206..13eb959 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -105,13 +105,9 @@
         return true;
       }
 
-      DexEncodedField resolveField = appInfoWithLiveness.resolveField(getField());
-      assert resolveField != null : "NoSuchFieldError (resolution failure) should be caught.";
-      if (appInfoWithLiveness.isFieldRead(resolveField.field)) {
-        return true;
-      }
-
-      return false;
+      DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
+      assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
+      return appInfoWithLiveness.isFieldRead(encodedField);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 45ca27a..1e839d7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -210,8 +210,9 @@
         Set<DexField> candidates =
             finalFieldPuts.stream()
                 .map(FieldInstruction::getField)
-                .map(field -> appInfoWithLiveness.resolveField(field).field)
+                .map(appInfoWithLiveness::resolveField)
                 .filter(appInfoWithLiveness::isStaticFieldWrittenOnlyInEnclosingStaticInitializer)
+                .map(field -> field.field)
                 .collect(Collectors.toSet());
 
         // Then retain only these fields that are actually no longer being written to.
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 5fdc8f5..92783d2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -622,8 +623,9 @@
     return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
   }
 
-  public boolean isFieldRead(DexField field) {
+  public boolean isFieldRead(DexEncodedField encodedField) {
     assert checkIfObsolete();
+    DexField field = encodedField.field;
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
     if (info != null && info.isRead()) {
       return true;
@@ -632,11 +634,12 @@
         // Fields in the class that is synthesized by D8/R8 would be used soon.
         || field.holder.isD8R8SynthesizedClassType()
         // For library classes we don't know whether a field is read.
-        || isLibraryOrClasspathField(field);
+        || isLibraryOrClasspathField(encodedField);
   }
 
-  public boolean isFieldWritten(DexField field) {
+  public boolean isFieldWritten(DexEncodedField encodedField) {
     assert checkIfObsolete();
+    DexField field = encodedField.field;
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
     if (info != null && info.isWritten()) {
       return true;
@@ -645,13 +648,13 @@
         // Fields in the class that is synthesized by D8/R8 would be used soon.
         || field.holder.isD8R8SynthesizedClassType()
         // For library classes we don't know whether a field is rewritten.
-        || isLibraryOrClasspathField(field);
+        || isLibraryOrClasspathField(encodedField);
   }
 
-  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexField field) {
+  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
-    return staticFieldsWrittenOnlyInEnclosingStaticInitializer.contains(field);
+    return staticFieldsWrittenOnlyInEnclosingStaticInitializer.contains(field.field);
   }
 
   public boolean mayPropagateValueFor(DexReference reference) {
@@ -659,8 +662,8 @@
     return !isPinned(reference) && !neverPropagateValue.contains(reference);
   }
 
-  private boolean isLibraryOrClasspathField(DexField field) {
-    DexClass holder = definitionFor(field.holder);
+  private boolean isLibraryOrClasspathField(DexEncodedField field) {
+    DexClass holder = definitionFor(field.field.holder);
     return holder == null || holder.isLibraryClass() || holder.isClasspathClass();
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 23e4503..19c297c 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -1347,9 +1347,9 @@
           DexEncodedField encodedField = appInfo.definitionFor(field);
           if (encodedField != null
               && (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.holder, appInfo))) {
-            assert appInfo.isFieldRead(field)
+            assert appInfo.isFieldRead(encodedField)
                 : "Expected kept field `" + field.toSourceString() + "` to be read";
-            assert appInfo.isFieldWritten(field)
+            assert appInfo.isFieldWritten(encodedField)
                 : "Expected kept field `" + field.toSourceString() + "` to be written";
           }
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 97b440d..c291f9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
@@ -194,9 +193,9 @@
   }
 
   private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
-      List<T> items, Predicate<S> live) {
+      List<T> items, Predicate<T> live) {
     for (int i = 0; i < items.size(); i++) {
-      if (!live.test(items.get(i).getKey())) {
+      if (!live.test(items.get(i))) {
         return i;
       }
     }
@@ -206,7 +205,8 @@
   private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
     AppInfoWithLiveness appInfo = appView.appInfo();
     InternalOptions options = appView.options();
-    int firstUnreachable = firstUnreachableIndex(methods, appInfo.liveMethods::contains);
+    int firstUnreachable =
+        firstUnreachableIndex(methods, method -> appInfo.liveMethods.contains(method.method));
     // Return the original array if all methods are used.
     if (firstUnreachable == -1) {
       return null;
@@ -266,7 +266,7 @@
 
   private DexEncodedField[] reachableFields(List<DexEncodedField> fields) {
     AppInfoWithLiveness appInfo = appView.appInfo();
-    Predicate<DexField> isReachableOrReferencedField =
+    Predicate<DexEncodedField> isReachableOrReferencedField =
         field -> appInfo.isFieldRead(field) || appInfo.isFieldWritten(field);
     int firstUnreachable = firstUnreachableIndex(fields, isReachableOrReferencedField);
     // Return the original array if all fields are used.
@@ -283,7 +283,7 @@
     }
     for (int i = firstUnreachable + 1; i < fields.size(); i++) {
       DexEncodedField field = fields.get(i);
-      if (isReachableOrReferencedField.test(field.field)) {
+      if (isReachableOrReferencedField.test(field)) {
         reachableOrReferencedFields.add(field);
       } else {
         if (Log.ENABLED) {