Version 2.0.99

Cherry pick: Fix class inliner error when singleton instance flows to static invoke
CL: https://r8-review.googlesource.com/c/r8/+/52741/

Cherry pick: Reproduce class inliner bug when singleton instance follows to static invoke
CL: https://r8-review.googlesource.com/c/r8/+/52740/

Cherry pick: Reproduce class inliner bug that incorrectly removed global side effect
CL: https://r8-review.googlesource.com/c/r8/+/52671/

Cherry pick: Restrict class inliner to maintain visible side-effects.
CL: https://r8-review.googlesource.com/c/r8/+/52600

Bug: 160942326
Change-Id: I3088e56c8184a9b2e4a409e67284852b09ccc412
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 126673b..5978320 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.98";
+  public static final String LABEL = "2.0.99";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
index 8d178a3..35eaaf5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
@@ -22,13 +22,16 @@
   final OptionalBool returnsReceiver;
 
   final boolean hasMonitorOnReceiver;
+  final boolean modifiesInstanceFields;
 
   public ClassInlinerEligibilityInfo(
       List<Pair<Invoke.Type, DexMethod>> callsReceiver,
       OptionalBool returnsReceiver,
-      boolean hasMonitorOnReceiver) {
+      boolean hasMonitorOnReceiver,
+      boolean modifiesInstanceFields) {
     this.callsReceiver = callsReceiver;
     this.returnsReceiver = returnsReceiver;
     this.hasMonitorOnReceiver = hasMonitorOnReceiver;
+    this.modifiesInstanceFields = modifiesInstanceFields;
   }
 }
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 2e44132..7546070 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
@@ -834,6 +834,12 @@
         // We will not be able to remove the monitor instruction afterwards.
         return null;
       }
+      if (eligibility.modifiesInstanceFields) {
+        // The static instance could be accessed from elsewhere. Therefore, we cannot
+        // allow side-effects to be removed and therefore cannot class inline method
+        // calls that modifies the instance.
+        return null;
+      }
     }
 
     // If the method returns receiver and the return value is actually
@@ -972,9 +978,10 @@
     }
 
     if (root.isStaticGet()) {
-      // If we are class inlining a singleton instance from a static-get, then we don't the value of
-      // the fields.
-      if (parameterUsage.hasFieldRead) {
+      // If we are class inlining a singleton instance from a static-get, then we don't know
+      // the value of the fields, and we also can't optimize away instance-field assignments, as
+      // they have global side effects.
+      if (parameterUsage.hasFieldAssignment || parameterUsage.hasFieldRead) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9fe7c0d..b989d2c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -167,6 +167,7 @@
     }
 
     List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
+    boolean modifiesInstanceFields = false;
     boolean seenSuperInitCall = false;
     boolean seenMonitor = false;
     for (Instruction insn : receiver.aliasedUsers()) {
@@ -190,6 +191,7 @@
           if (instancePutInstruction.value().getAliasedValue() == receiver) {
             return;
           }
+          modifiesInstanceFields = true;
         }
         DexField field = insn.asFieldInstruction().getField();
         if (appView.appInfo().resolveFieldOn(clazz, field) != null) {
@@ -258,7 +260,8 @@
         new ClassInlinerEligibilityInfo(
             callsReceiver,
             new ClassInlinerReceiverAnalysis(appView, method, code).computeReturnsReceiver(),
-            seenMonitor || synchronizedVirtualMethod));
+            seenMonitor || synchronizedVirtualMethod,
+            modifiesInstanceFields));
   }
 
   private void identifyParameterUsages(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningTest.java
new file mode 100644
index 0000000..17ccc82
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2020, 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.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StatefulSingletonClassInliningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StatefulSingletonClassInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StatefulSingletonClassInliningTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      State.get().set();
+      State.get().print();
+    }
+  }
+
+  static class State {
+
+    static StatefulSingleton SINGLETON = new StatefulSingleton();
+
+    static StatefulSingleton get() {
+      return SINGLETON;
+    }
+  }
+
+  static class StatefulSingleton {
+
+    boolean field;
+
+    @NeverInline
+    void set() {
+      field = true;
+    }
+
+    void print() {
+      System.out.println(field);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java
new file mode 100644
index 0000000..681f958
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/StatefulSingletonClassInliningWithStaticEscapeTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, 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.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StatefulSingletonClassInliningWithStaticEscapeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StatefulSingletonClassInliningWithStaticEscapeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(StatefulSingletonClassInliningWithStaticEscapeTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      State.set(State.get());
+      State.get().print();
+    }
+  }
+
+  static class State {
+
+    static StatefulSingleton SINGLETON = new StatefulSingleton();
+
+    static StatefulSingleton get() {
+      return SINGLETON;
+    }
+
+    @NeverInline
+    static void set(StatefulSingleton state) {
+      state.field = true;
+    }
+  }
+
+  static class StatefulSingleton {
+
+    boolean field;
+
+    void print() {
+      System.out.println(field);
+    }
+  }
+}