Retain field access information for null valued fields

Change-Id: Ia6ccf06e260453c285d4e5433e4bfb50588529e0
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 1537fb2..e9c2fc5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -57,6 +57,11 @@
   }
 
   @Override
+  public <T> T apply(Function<DexField, T> fieldConsumer, Function<DexMethod, T> methodConsumer) {
+    return fieldConsumer.apply(this);
+  }
+
+  @Override
   public <T> T apply(
       Function<DexType, T> classConsumer,
       Function<DexField, T> fieldConsumer,
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 790fd33..1c02f3b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.google.common.collect.Iterables;
+import java.util.function.Function;
 
 public abstract class DexMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
     extends DexReference implements NamingLensComparable<R> {
@@ -18,6 +19,9 @@
     this.name = name;
   }
 
+  public abstract <T> T apply(
+      Function<DexField, T> fieldConsumer, Function<DexMethod, T> methodConsumer);
+
   public abstract DexEncodedMember<?, ?> lookupOnClass(DexClass clazz);
 
   public abstract ProgramMember<?, ?> lookupOnProgramClass(DexProgramClass clazz);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 8ab4935..e00c1fe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -67,6 +67,11 @@
   }
 
   @Override
+  public <T> T apply(Function<DexField, T> fieldConsumer, Function<DexMethod, T> methodConsumer) {
+    return methodConsumer.apply(this);
+  }
+
+  @Override
   public <T> T apply(
       Function<DexType, T> classConsumer,
       Function<DexField, T> fieldConsumer,
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 249439c..376abd6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -115,18 +115,21 @@
   }
 
   public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
-    if (isClassType()) {
-      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(this));
-      if (clazz == null) {
-        return false;
-      }
-      if (appView.options().enableUninstantiatedTypeOptimizationForInterfaces) {
-        return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
-      } else {
-        return !clazz.isInterface() && !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
-      }
+    return isAlwaysNull(appView.appInfo());
+  }
+
+  public boolean isAlwaysNull(AppInfoWithLiveness appInfo) {
+    if (!isClassType()) {
+      return false;
     }
-    return false;
+    DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(this));
+    if (clazz == null) {
+      return false;
+    }
+    if (appInfo.options().enableUninstantiatedTypeOptimizationForInterfaces) {
+      return !appInfo.isInstantiatedDirectlyOrIndirectly(clazz);
+    }
+    return !clazz.isInterface() && !appInfo.isInstantiatedDirectlyOrIndirectly(clazz);
   }
 
   public boolean isSamePackage(DexType other) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 615e5ae..39ce729 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -360,7 +360,9 @@
     }
 
     AbstractValue abstractValue;
-    if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+    if (field.getType().isAlwaysNull(appView)) {
+      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = target.getOptimizationInfo().getAbstractValue();
       if (abstractValue.isUnknown() && !target.isStatic()) {
         AbstractValue abstractReceiverValue =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index c681eaa..d60ba25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -14,8 +14,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 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.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
@@ -97,18 +95,11 @@
 
   public UninstantiatedTypeOptimization strenghtenOptimizationInfo() {
     OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
-        appView.appInfo().getFieldAccessInfoCollection();
     AbstractValue nullValue = appView.abstractValueFactory().createSingleNumberValue(0);
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.forEachField(
           field -> {
             if (field.type().isAlwaysNull(appView)) {
-              FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
-              if (fieldAccessInfo != null) {
-                // Clear all writes since each write must write `null` to the field.
-                fieldAccessInfo.asMutable().clearWrites();
-              }
               feedback.recordFieldHasAbstractValue(field, appView, nullValue);
             }
           });
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 e12ec89..855e24c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -839,11 +839,31 @@
         && !keepInfo.getMethodInfo(method).isPinned();
   }
 
-  public boolean mayPropagateValueFor(DexReference reference) {
+  public boolean mayPropagateValueFor(DexMember<?, ?> reference) {
     assert checkIfObsolete();
-    return options().enableValuePropagation
-        && !isPinned(reference)
-        && !neverPropagateValue.contains(reference);
+    return reference.apply(this::mayPropagateValueFor, this::mayPropagateValueFor);
+  }
+
+  public boolean mayPropagateValueFor(DexField field) {
+    assert checkIfObsolete();
+    if (!options().enableValuePropagation || neverPropagateValue.contains(field)) {
+      return false;
+    }
+    if (isPinned(field) && !field.getType().isAlwaysNull(this)) {
+      return false;
+    }
+    return true;
+  }
+
+  public boolean mayPropagateValueFor(DexMethod method) {
+    assert checkIfObsolete();
+    if (!options().enableValuePropagation || neverPropagateValue.contains(method)) {
+      return false;
+    }
+    if (isPinned(method) && !method.getReturnType().isAlwaysNull(this)) {
+      return false;
+    }
+    return true;
   }
 
   private boolean isLibraryOrClasspathField(DexEncodedField field) {
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index c43c289..689e080 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -25,7 +25,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Calendar;
 import java.util.List;
 import java.util.TreeSet;
 
@@ -46,11 +45,12 @@
 
   protected abstract List<Class<?>> getMethodTemplateClasses();
 
-  public static String getHeaderString(Class<?> generationClass, String generatedPackage) {
-    int year = Calendar.getInstance().get(Calendar.YEAR);
-    String simpleName = generationClass.getSimpleName();
+  protected abstract int getYear();
+
+  public String getHeaderString() {
+    String simpleName = getClass().getSimpleName();
     return StringUtils.lines(
-        "// Copyright (c) " + year + ", the R8 project authors. Please see the AUTHORS file",
+        "// Copyright (c) " + getYear() + ", 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.",
         "",
@@ -58,7 +58,7 @@
         "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
         "// ***********************************************************************************",
         "",
-        "package " + generatedPackage + ";");
+        "package " + getGeneratedClassPackageName() + ";");
   }
 
   protected Path getGeneratedFile() {
@@ -115,7 +115,7 @@
 
   private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
     try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
-      printer.print(getHeaderString(this.getClass(), getGeneratedClassPackageName()));
+      printer.print(getHeaderString());
       printer.println("import com.android.tools.r8.graph.DexItemFactory;");
       codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
       printer.println("public final class " + getGeneratedClassName() + " {\n");
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
index 54a405c..74bf88f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/GenerateEnumUnboxingMethods.java
@@ -50,6 +50,10 @@
     return METHOD_TEMPLATE_CLASSES;
   }
 
+  @Override
+  protected int getYear() {
+    return 2020;
+  }
 
   @Test
   public void testEnumUtilityMethodsGenerated() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 05c11c4..d78a1c7 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -68,6 +68,11 @@
     return METHOD_TEMPLATE_CLASSES;
   }
 
+  @Override
+  protected int getYear() {
+    return 2020;
+  }
+
   @Test
   public void testBackportsGenerated() throws Exception {
     ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
index c35135e..732952d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/GenerateCfUtilityMethodsForCodeOptimizations.java
@@ -43,6 +43,11 @@
     return ImmutableList.of(CfUtilityMethodsForCodeOptimizationsTemplates.class);
   }
 
+  @Override
+  protected int getYear() {
+    return 2020;
+  }
+
   @Test
   public void test() throws Exception {
     ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());