Member value propagation for null's

Change-Id: I3a6f234d1d66db70c77133f06121e79b47304a20
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 7b57fa2..670e510 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -5,10 +5,10 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
 
 public class DexEncodedField extends KeyedDexItem<DexField> {
   public static final DexEncodedField[] EMPTY_ARRAY = {};
@@ -141,21 +141,49 @@
     return staticValue == null ? DexValue.defaultForType(field.type) : staticValue;
   }
 
-  // Returns a const instructions if this field is a compile time final const.
-  public Instruction valueAsConstInstruction(
-      AppInfoWithLiveness appInfo, Value dest, InternalOptions options) {
-    // 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(this)) && !appInfo.isPinned(field);
-    if (!isEffectivelyFinal) {
-      return null;
+  /**
+   * Returns a const instructions if this field is a compile time final const.
+   *
+   * <p>NOTE: It is the responsibility of the caller to check if this field is pinned or not.
+   */
+  public ConstInstruction valueAsConstInstruction(
+      IRCode code, Value dest, AppView<AppInfoWithLiveness> appView) {
+    // If it is a static field, we can only propagate the value if class initialization does not
+    // have side effects.
+    if (isStatic()) {
+      DexClass clazz = appView.definitionFor(field.holder);
+      if (clazz == null) {
+        return null;
+      }
+      DexType context = code.method.method.holder;
+      if (clazz.classInitializationMayHaveSideEffects(
+          appView,
+          // Types that are a super type of the current context are guaranteed to be initialized
+          // already.
+          type -> appView.isSubtype(context, type).isTrue())) {
+        return null;
+      }
     }
-    if (accessFlags.isStatic()) {
-      DexClass clazz = appInfo.definitionFor(field.holder);
-      assert clazz != null : "Class for the field must be present";
-      return getStaticValue().asConstInstruction(clazz.hasClassInitializer(), dest, options);
+
+    boolean isWritten = appView.appInfo().isFieldWrittenByFieldPutInstruction(this);
+    if (!isWritten) {
+      // Since the field is not written, we can simply return the default value for the type.
+      return getStaticValue().asConstInstruction(code, dest, appView.options());
     }
+
+    // The only way to figure out whether the DexValue contains the final value is ensure the value
+    // is not the default or check that <clinit> is not present.
+    if (accessFlags.isFinal() && isStatic()) {
+      DexClass clazz = appView.definitionFor(field.holder);
+      if (clazz == null || clazz.hasClassInitializer()) {
+        return null;
+      }
+      DexValue staticValue = getStaticValue();
+      if (!staticValue.isDefault(field.type)) {
+        return staticValue.asConstInstruction(code, dest, appView.options());
+      }
+    }
+
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 782f04a..3752054 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
 import com.android.tools.r8.utils.EncodedValueUtils;
@@ -137,9 +138,8 @@
 
   public abstract Object getBoxedValue();
 
-  // Returns a const instruction for the non default value.
-  public ConstInstruction asConstInstruction(
-      boolean hasClassInitializer, Value dest, InternalOptions options) {
+  /** Returns an instruction that can be used to materialize this {@link DexValue} (or null). */
+  public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
     return null;
   }
 
@@ -214,8 +214,7 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
       return null;
     }
   }
@@ -302,9 +301,8 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
-      return (this == DEFAULT && hasClassInitializer) ? null : new ConstNumber(dest, value);
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
+      return new ConstNumber(dest, value);
     }
   }
 
@@ -359,9 +357,8 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
-      return (this == DEFAULT && hasClassInitializer) ? null : new ConstNumber(dest, value);
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
+      return new ConstNumber(dest, value);
     }
   }
 
@@ -420,9 +417,8 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
-      return (this == DEFAULT && hasClassInitializer) ? null : new ConstNumber(dest, value);
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
+      return new ConstNumber(dest, value);
     }
   }
 
@@ -477,9 +473,8 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
-      return (this == DEFAULT && hasClassInitializer) ? null : new ConstNumber(dest, value);
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
+      return new ConstNumber(dest, value);
     }
   }
 
@@ -534,9 +529,8 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
-      return (this == DEFAULT && hasClassInitializer) ? null : new ConstNumber(dest, value);
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
+      return new ConstNumber(dest, value);
     }
   }
 
@@ -739,8 +733,7 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
       ConstString instruction =
           new ConstString(dest, value, ThrowingInfo.defaultForConstString(options));
       if (!instruction.instructionInstanceCanThrow()) {
@@ -784,8 +777,7 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
       DexItemBasedConstString instruction =
           new DexItemBasedConstString(
               dest, value, ThrowingInfo.defaultForConstString(options), classNameComputationInfo);
@@ -1122,9 +1114,8 @@
     }
 
     @Override
-    public ConstInstruction asConstInstruction(
-        boolean hasClassInitializer, Value dest, InternalOptions options) {
-      return (this == DEFAULT && hasClassInitializer) ? null : new ConstNumber(dest, value ? 1 : 0);
+    public ConstInstruction asConstInstruction(IRCode code, Value dest, InternalOptions options) {
+      return new ConstNumber(dest, value ? 1 : 0);
     }
   }
 
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 df73ae8..ccd346b 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -36,6 +35,8 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Set;
@@ -44,6 +45,10 @@
 public class MemberValuePropagation {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final Reporter reporter;
+
+  // Fields for which we have reported warnings to due Proguard configuration rules.
+  private final Set<DexField> warnedFields = Sets.newIdentityHashSet();
 
   private enum RuleType {
     NONE,
@@ -78,6 +83,7 @@
 
   public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
+    this.reporter = appView.options().reporter;
   }
 
   private boolean mayPropagateValueFor(DexEncodedField field) {
@@ -134,13 +140,30 @@
 
       DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field.holder, field);
       if (staticField == null) {
-        throw new CompilationError(field.holder.toSourceString() + "." + field.name.toString()
-            + " used in assumevalues rule does not exist.");
+        if (warnedFields.add(field)) {
+          reporter.warning(
+              new StringDiagnostic(
+                  "Field `"
+                      + field.toSourceString()
+                      + "` is used in an -assumevalues rule but does not exist.",
+                  code.origin));
+        }
+        return null;
       }
 
       Value value = code.createValue(typeLattice, instruction.getLocalInfo());
-      ConstInstruction replacement =
-          staticField.getStaticValue().asConstInstruction(false, value, appView.options());
+      ConstInstruction replacement = staticField.valueAsConstInstruction(code, value, appView);
+      if (replacement == null) {
+        reporter.warning(
+            new StringDiagnostic(
+                "Unable to apply the rule `"
+                    + returnValueRule.toString()
+                    + "`: Could not determine the value of field `"
+                    + field.toSourceString()
+                    + "`",
+                code.origin));
+        return null;
+      }
       if (replacement.isDexItemBasedConstString()) {
         code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
       }
@@ -309,18 +332,21 @@
     if (target == null || !mayPropagateValueFor(target)) {
       return;
     }
+
     // Check if a this value is known const.
-    Instruction replacement =
-        target.valueAsConstInstruction(appView.appInfo(), current.dest(), appView.options());
-    if (replacement != null) {
-      affectedValues.add(replacement.outValue());
-      iterator.replaceCurrentInstruction(replacement);
-      if (replacement.isDexItemBasedConstString()) {
-        code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+    if (!appView.appInfo().isPinned(target.field)) {
+      ConstInstruction replacement = target.valueAsConstInstruction(code, current.dest(), appView);
+      if (replacement != null) {
+        affectedValues.add(replacement.outValue());
+        iterator.replaceCurrentInstruction(replacement);
+        if (replacement.isDexItemBasedConstString()) {
+          code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+        }
+        target.getMutableOptimizationInfo().markAsPropagated();
+        return;
       }
-      target.getMutableOptimizationInfo().markAsPropagated();
-      return;
     }
+
     ProguardMemberRuleLookup lookup = lookupMemberRule(target);
     if (lookup != null
         && tryConstantReplacementFromProguard(
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 92783d2..77ea68b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -639,16 +639,26 @@
 
   public boolean isFieldWritten(DexEncodedField encodedField) {
     assert checkIfObsolete();
+    return isFieldWrittenByFieldPutInstruction(encodedField) || isPinned(encodedField.field);
+  }
+
+  public boolean isFieldWrittenByFieldPutInstruction(DexEncodedField encodedField) {
+    assert checkIfObsolete();
     DexField field = encodedField.field;
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
     if (info != null && info.isWritten()) {
+      // The field is written directly by the program itself.
       return true;
     }
-    return isPinned(field)
-        // 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(encodedField);
+    if (field.holder.isD8R8SynthesizedClassType()) {
+      // Fields in the class that is synthesized by D8/R8 would be used soon.
+      return true;
+    }
+    if (isLibraryOrClasspathField(encodedField)) {
+      // For library classes we don't know whether a field is rewritten.
+      return true;
+    }
+    return false;
   }
 
   public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
index a8dfd4c..e9195fd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -34,7 +35,7 @@
 class StringInMonitorTestMain {
   static final Object lock = new Object();
 
-  private static int foo = 0;
+  @NeverPropagateValue private static int foo = 0;
 
   @NeverInline
   private static synchronized void sync() throws OutOfMemoryError {
@@ -140,11 +141,28 @@
     }
 
     MethodSubject sync = mainClass.uniqueMethodWithName("sync");
+    System.out.println(sync.getMethod().getCode());
     assertThat(sync, isPresent());
     count = Streams.stream(sync.iterateInstructions(
         i -> i.isConstString("", JumboStringMode.ALLOW))).count();
     assertEquals(expectedConstStringCount2, count);
 
+    /*.limit stack 2
+    .limit locals 1
+     0:   ldc bar
+     1:   ldc
+     2:   if_acmpeq L0
+     3:   return
+     4: L0: ; locals:
+     5:   ; frame: [] []
+     6:   getstatic java/lang/System/out Ljava/io/PrintStream;
+     7:   ldc bar
+     8:   invokevirtual java.io.PrintStream.println(Ljava/lang/String;)V
+     9:   new java/lang/OutOfMemoryError
+    10:   dup
+    11:   invokespecial java.lang.OutOfMemoryError.<init>()V
+    12:   athrow*/
+
     // In CF, we don't explicitly add monitor-{enter|exit} and catch-all for synchronized methods.
     if (parameters.isDexRuntime()) {
       Iterator<InstructionSubject> constStringIterator =
@@ -203,6 +221,7 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(MAIN)
             .enableInliningAnnotations()
+            .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(MAIN)
             .setMinApi(parameters.getRuntime())
             .run(parameters.getRuntime(), MAIN)