Fix removal of unused materialized strings

Change-Id: I6ef39bd330f1cee6091eba10ca4ffebe915fe910
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 c0aaa95..8fe1c7e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2172,6 +2172,10 @@
       assert false : "Unexpected invoke targeting `" + invokedMethod.toSourceString() +  "`";
       return false;
     }
+
+    public boolean isAppendCharArrayMethod(DexMethod method) {
+      return method == appendCharArray || method == appendSubCharArray;
+    }
   }
 
   public class SupplierMembers extends LibraryMembers {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 2606826..a2a90b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -284,7 +284,7 @@
               } else if (oracle.isAppend(instruction)) {
                 AppendNode appendNode = createAppendNode(instruction.asInvokeVirtual());
                 appendNode.setConstantArgument(oracle.getConstantArgument(instruction));
-                Value arg = invoke.getOperand(1).getAliasedValue();
+                Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue();
                 if (oracle.hasStringBuilderType(arg)) {
                   insertImplicitToStringNode(
                       arg, instruction, appendNode, escapeState, nodeConsumer);
@@ -508,7 +508,7 @@
               escaping.add(root);
             }
             if (next.isToStringNode() || next.isImplicitToStringNode()) {
-              materializingInstructions.add(root);
+              materializingInstructions.add(next);
             }
             if (next.isInspectingNode()) {
               inspectingCapacity.add(root);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
index 01be10c..74b654d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
@@ -167,7 +167,6 @@
         ToStringNode toStringNode = currentNode.asToStringNode();
         munchingState.actions.put(
             toStringNode.getInstruction(), new ReplaceByConstantString(constantArgument));
-        munchingState.materializingInstructions.get(originalRoot).remove(currentNode);
         String oldValue =
             munchingState.optimizedStrings.put(
                 toStringNode.getInstruction().outValue(), constantArgument);
@@ -180,6 +179,7 @@
         munchingState.actions.put(
             initOrAppend.getInstruction(), new AppendWithNewConstantString(constantArgument));
       }
+      munchingState.materializingInstructions.get(originalRoot).remove(currentNode);
       currentNode.removeNode();
       return true;
     }
@@ -261,7 +261,7 @@
                   && currentNode.getSuccessors().isEmpty();
           if (canRemoveIfNoInspectionOrMaterializing
               && canRemoveIfLastAndNoLoop
-              && munchingState.oracle.canObserveStringBuilderCall(
+              && !munchingState.oracle.canObserveStringBuilderCall(
                   currentNode.asAppendNode().getInstruction())) {
             munchingState.actions.put(
                 appendNode.getInstruction(), RemoveStringBuilderAction.getInstance());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
index d7d7f59..063f791 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import java.util.List;
@@ -109,7 +110,8 @@
       if (!instruction.isInvokeMethodWithReceiver()) {
         return null;
       }
-      if (isAppend(instruction)) {
+      if (isAppend(instruction)
+          && !isAppendWithSubArray(instruction.asInvokeMethodWithReceiver())) {
         return getConstantStringForAppend(instruction.asInvokeVirtual());
       } else if (isInit(instruction)) {
         return getConstantStringForInit(instruction.asInvokeDirect());
@@ -165,14 +167,16 @@
         return false;
       }
       DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
-      if (factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod)
-          || factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod)) {
-        return false;
-      }
       return factory.stringBuilderMethods.isAppendMethod(invokedMethod)
           || factory.stringBufferMethods.isAppendMethod(invokedMethod);
     }
 
+    public boolean isAppendWithSubArray(InvokeMethodWithReceiver instruction) {
+      DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
+      return factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod)
+          || factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod);
+    }
+
     @Override
     public boolean canObserveStringBuilderCall(Instruction instruction) {
       if (!instruction.isInvokeMethod()) {
@@ -185,7 +189,11 @@
           || factory.stringBuilderMethods.charSequenceConstructor == invokedMethod
           || factory.stringBufferMethods.charSequenceConstructor == invokedMethod) {
         assert instruction.inValues().size() == 2;
-        return instruction.inValues().get(1).getType().isStringType(factory);
+        return !instruction.inValues().get(1).getType().isStringType(factory);
+      }
+      if (factory.stringBuilderMethods.isAppendCharArrayMethod(invokedMethod)
+          || factory.stringBufferMethods.isAppendCharArrayMethod(invokedMethod)) {
+        return instruction.asInvokeVirtual().getFirstNonReceiverArgument().isMaybeNull();
       }
       return false;
     }