Treat instance-put as having side-effects if it may store an object with a non-default finalize()

Bug: 137836288
Change-Id: I939386a925a8eba94eeb27a90d180c1c89a9316f
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 460885c..e4f80a81 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.SetUtils;
@@ -714,8 +715,17 @@
     return !isSubtype(type1, type2) && !isSubtype(type2, type1);
   }
 
-  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(DexType type) {
-    return computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(type, true);
+  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeLatticeElement type) {
+    Set<DexType> interfaces = type.getInterfaces();
+    if (!interfaces.isEmpty()) {
+      for (DexType interfaceType : interfaces) {
+        if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
+          return true;
+        }
+      }
+      return false;
+    }
+    return computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(type.getClassType(), true);
   }
 
   private boolean computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(
@@ -727,8 +737,10 @@
     }
     DexClass clazz = definitionFor(type);
     if (clazz == null) {
-      mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, true);
-      return true;
+      // This is strictly not conservative but is needed to avoid that we treat Object as having
+      // a subtype that has a non-default finalize() implementation.
+      mayHaveFinalizeMethodDirectlyOrIndirectlyCache.put(type, false);
+      return false;
     }
     if (clazz.isProgramClass()) {
       if (lookUpwards) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index b95376e..72e4c9d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -8,13 +8,16 @@
 import com.android.tools.r8.graph.AppView;
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collections;
 import java.util.List;
@@ -163,4 +166,39 @@
     assert isFieldPut();
     return EmptyFieldSet.getInstance();
   }
+
+  /**
+   * Returns {@code true} if this instruction may store an instance of a class that has a non-
+   * default finalize() method in a field. In that case, it is not safe to remove this instruction,
+   * since that could change the lifetime of the value.
+   */
+  boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo) {
+    assert isFieldPut();
+    TypeLatticeElement type = value().getTypeLattice();
+    TypeLatticeElement baseType =
+        type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
+    if (baseType.isClassType()) {
+      Value root = value().getAliasedValue();
+      if (!root.isPhi() && root.definition.isNewInstance()) {
+        DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
+        if (clazz == null) {
+          return true;
+        }
+        if (clazz.superType == null) {
+          return false;
+        }
+        DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+        DexEncodedMethod resolutionResult =
+            appInfo
+                .resolveMethod(clazz.type, dexItemFactory.objectMethods.finalize)
+                .asSingleTarget();
+        return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
+      }
+
+      return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(
+          baseType.asClassTypeLatticeElement());
+    }
+
+    return false;
+  }
 }
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 4dcb749..32484c3 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
@@ -110,7 +110,8 @@
 
       DexEncodedField encodedField = appInfoWithLiveness.resolveField(getField());
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
-      return appInfoWithLiveness.isFieldRead(encodedField);
+      return appInfoWithLiveness.isFieldRead(encodedField)
+          || isStoringObjectWithFinalizer(appInfoWithLiveness);
     }
 
     // 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 5d8fb67..63e5c10 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
@@ -15,16 +15,12 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-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.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -132,40 +128,6 @@
     return true;
   }
 
-  /**
-   * Returns {@code true} if this instruction may store a value that has a non-default finalize()
-   * method in a static field. In that case, it is not safe to remove this instruction, since that
-   * could change the lifetime of the value.
-   */
-  private boolean isStoringObjectWithFinalizer(AppInfoWithLiveness appInfo) {
-    TypeLatticeElement type = value().getTypeLattice();
-    TypeLatticeElement baseType =
-        type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
-    if (baseType.isClassType()) {
-      Value root = value().getAliasedValue();
-      if (!root.isPhi() && root.definition.isNewInstance()) {
-        DexClass clazz = appInfo.definitionFor(root.definition.asNewInstance().clazz);
-        if (clazz == null) {
-          return true;
-        }
-        if (clazz.superType == null) {
-          return false;
-        }
-        DexItemFactory dexItemFactory = appInfo.dexItemFactory();
-        DexEncodedMethod resolutionResult =
-            appInfo
-                .resolveMethod(clazz.type, dexItemFactory.objectMethods.finalize)
-                .asSingleTarget();
-        return resolutionResult != null && resolutionResult.isProgramMethod(appInfo);
-      }
-
-      return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(
-          baseType.asClassTypeLatticeElement().getClassType());
-    }
-
-    return false;
-  }
-
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
     // static-put can be dead as long as it cannot have any of the following: