Version 2.0.31

Cherry-pick: Use a seen set when computing if class initialization has side effects.
CL: https://r8-review.googlesource.com/c/r8/+/48860
Bug: 147865209
Change-Id: I107a041c20f85e99cd4c21e08eda2de8f5763740
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 7617447..8a61a6c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.0.30";
+  public static final String LABEL = "2.0.31";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 815e721..7d63446 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -833,11 +833,15 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
-    return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return classInitializationMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean classInitializationMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
+    if (!seen.add(type)) {
+      return false;
+    }
     if (ignore.test(type)) {
       return false;
     }
@@ -855,7 +859,7 @@
     if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
       return true;
     }
-    return initializationOfParentTypesMayHaveSideEffects(appView, ignore);
+    return initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
   }
 
   private boolean hasClassInitializerThatCannotBePostponed() {
@@ -880,17 +884,19 @@
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
-    return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return initializationOfParentTypesMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     for (DexType iface : interfaces.values) {
-      if (iface.classInitializationMayHaveSideEffects(appView, ignore)) {
+      if (iface.classInitializationMayHaveSideEffects(appView, ignore, seen)) {
         return true;
       }
     }
-    if (superType != null && superType.classInitializationMayHaveSideEffects(appView, ignore)) {
+    if (superType != null
+        && superType.classInitializationMayHaveSideEffects(appView, ignore, seen)) {
       return true;
     }
     return false;
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 1764634..3825632 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 
 public class DexEncodedField extends KeyedDexItem<DexField> {
   public static final DexEncodedField[] EMPTY_ARRAY = {};
@@ -208,7 +209,8 @@
         appView,
         // Types that are a super type of the current context are guaranteed to be initialized
         // already.
-        type -> appView.isSubtype(context, type).isTrue())) {
+        type -> appView.isSubtype(context, type).isTrue(),
+        Sets.newIdentityHashSet())) {
       // Ignore class initialization side-effects for dead proto extension fields to ensure that
       // we force replace these field reads by null.
       boolean ignore =
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 9a8a73c..08d4d61 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -25,9 +25,11 @@
 import com.android.tools.r8.utils.Pair;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
 
 public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -60,23 +62,26 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
-    return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return classInitializationMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean classInitializationMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     DexClass clazz = appView.definitionFor(this);
-    return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore);
+    return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore, seen);
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
-    return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
+    return initializationOfParentTypesMayHaveSideEffects(
+        appView, Predicates.alwaysFalse(), Sets.newIdentityHashSet());
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(
-      AppView<?> appView, Predicate<DexType> ignore) {
+      AppView<?> appView, Predicate<DexType> ignore, Set<DexType> seen) {
     DexClass clazz = appView.definitionFor(this);
-    return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore);
+    return clazz == null
+        || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore, seen);
   }
 
   public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
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 3cae1e4..2dd3c7d 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
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.List;
 
@@ -128,7 +129,8 @@
       if (field.holder.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue())) {
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet())) {
         return AbstractError.top();
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 0c4671e..68a02e9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -199,7 +200,8 @@
                     appView,
                     // Types that are a super type of `context` are guaranteed to be initialized
                     // already.
-                    type -> appView.isSubtype(context, type).isTrue());
+                    type -> appView.isSubtype(context, type).isTrue(),
+                    Sets.newIdentityHashSet());
       }
 
       return targetMayHaveSideEffects;
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 407742e..6efcc1b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
 
 public class NewInstance extends Instruction {
 
@@ -167,7 +168,8 @@
     if (definition.classInitializationMayHaveSideEffects(
         appView,
         // Types that are a super type of `context` are guaranteed to be initialized already.
-        type -> appView.isSubtype(context, type).isTrue())) {
+        type -> appView.isSubtype(context, type).isTrue(),
+        Sets.newIdentityHashSet())) {
       return true;
     }
 
@@ -207,7 +209,8 @@
       return clazz.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue());
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     } else {
       // In D8, this instruction may trigger class initialization if the holder of the field is
       // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index f59eb4b..08285c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.google.common.collect.Sets;
 import java.util.Set;
 
 public class StaticGet extends FieldInstruction {
@@ -236,7 +237,8 @@
       return holder.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue());
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     } else {
       // In D8, this instruction may trigger class initialization if the holder of the field is
       // different from the current context.
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 63e5c10..c9bab8b 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
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.google.common.collect.Sets;
 
 public class StaticPut extends FieldInstruction {
 
@@ -235,7 +236,8 @@
       return holder.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of `context` are guaranteed to be initialized already.
-          type -> appView.isSubtype(context, type).isTrue());
+          type -> appView.isSubtype(context, type).isTrue(),
+          Sets.newIdentityHashSet());
     } else {
       // In D8, this instruction may trigger class initialization if the holder of the field is
       // different from the current context.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index 5577a8c..f362ef7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -186,7 +186,8 @@
           if (newInstance.clazz.classInitializationMayHaveSideEffects(
               appView,
               // Types that are a super type of `context` are guaranteed to be initialized already.
-              type -> appView.isSubtype(context, type).isTrue())) {
+              type -> appView.isSubtype(context, type).isTrue(),
+              Sets.newIdentityHashSet())) {
             killAllActiveFields();
           }
         } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 452daab..5683361 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -206,7 +206,8 @@
       if (eligibleClassDefinition.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of the current context are guaranteed to be initialized.
-          type -> appView.isSubtype(method.method.holder, type).isTrue())) {
+          type -> appView.isSubtype(method.method.holder, type).isTrue(),
+          Sets.newIdentityHashSet())) {
         return EligibilityStatus.HAS_CLINIT;
       }
       return EligibilityStatus.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 309e7e4..ca31f9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -420,7 +420,8 @@
     // We basically can't move the clinit, since it is not called when implementing classes have
     // their clinit called - except when the interface has a default method.
     if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
-        || targetClass.classInitializationMayHaveSideEffects(appView, type -> type == clazz.type)
+        || targetClass.classInitializationMayHaveSideEffects(
+            appView, type -> type == clazz.type, Sets.newIdentityHashSet())
         || (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appView))) {
       // TODO(herhut): Handle class initializers.
       if (Log.ENABLED) {