Check for out-value in string builder optimizer

Change-Id: I1a79c066a10aa1c43aefeb3de693abc353ce8dd6
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 58b768d..fe33ab9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -572,7 +572,7 @@
           createString("makeConcat")
       );
 
-  public final Set<DexMethod> libraryMethodsReturningReceiver =
+  public Set<DexMethod> libraryMethodsReturningReceiver =
       ImmutableSet.<DexMethod>builder()
           .addAll(stringBufferMethods.appendMethods)
           .addAll(stringBuilderMethods.appendMethods)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 8e8d2da..6c714c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -828,6 +828,9 @@
     public boolean isSupportedAppendMethod(InvokeMethod invoke) {
       DexMethod invokedMethod = invoke.getInvokedMethod();
       assert isAppendMethod(invokedMethod);
+      if (invoke.hasOutValue()) {
+        return false;
+      }
       // Any methods other than append(arg) are not trivial since they may change the builder
       // state not monotonically.
       if (invoke.inValues().size() > 2) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java
new file mode 100644
index 0000000..2c61465
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithEscapingAliasTest.java
@@ -0,0 +1,54 @@
+// 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.string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.Sets;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringBuilderWithEscapingAliasTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringBuilderWithEscapingAliasTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .addOptionsModification(
+            options ->
+                options.itemFactory.libraryMethodsReturningReceiver = Sets.newIdentityHashSet())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(StringUtils.times(StringUtils.lines("Hello world!"), 2));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      StringBuilder builder = new StringBuilder();
+      StringBuilder alias = builder.append("Hello");
+      builder.append(" world!");
+      System.out.println(builder.toString());
+      System.out.println(alias.toString());
+    }
+  }
+}