Account for Objects.toString taking string builder as arg

Bug: b/219455761
Change-Id: I34c26ce3b2282d1627f8288783bd8b97db97cb37
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 830516e..2606826 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode;
@@ -262,7 +263,7 @@
               assert newInstanceValue != null;
               nodeConsumer.accept(
                   newInstanceValue, createNewInstanceNode(instruction.asNewInstance()));
-            } else {
+            } else if (instruction.isInvokeMethodWithReceiver()) {
               InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
               Value receiver = invoke.getReceiver();
               if (oracle.isInit(instruction)) {
@@ -316,6 +317,17 @@
                     escaped ->
                         nodeConsumer.accept(escaped, createOtherStringBuilderNode(instruction)));
               }
+            } else {
+              assert instruction.isInvokeStatic();
+              InvokeStatic invoke = instruction.asInvokeStatic();
+              assert invoke.getInvokedMethod()
+                  == appView.dexItemFactory().objectsMethods.toStringWithObject;
+              visitStringBuilderValues(
+                  invoke.getFirstOperand(),
+                  escapeState,
+                  actual ->
+                      nodeConsumer.accept(actual, createToStringNode(instruction.asInvokeMethod())),
+                  escaped -> nodeConsumer.accept(escaped, createInspectionNode(instruction)));
             }
           }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
index bc92a0f..e2d46af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderEscapeTransferFunction.java
@@ -63,7 +63,8 @@
       }
     }
     if (isStringBuilderInstruction) {
-      if (instruction.isInvokeMethodWithReceiver()) {
+      if (instruction.isInvokeMethod()) {
+        assert !instruction.inValues().isEmpty();
         Value firstOperand = instruction.getFirstOperand();
         if (!builder.getLiveStringBuilders().contains(firstOperand)) {
           // We can have constant NULL being the first operand, which we have not marked as
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
index 22d5c70..c189299 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNode.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.google.common.collect.Sets;
@@ -426,9 +427,9 @@
    */
   static class ToStringNode extends StringBuilderNode implements StringBuilderInstruction {
 
-    private final InvokeVirtual instruction;
+    private final InvokeMethod instruction;
 
-    private ToStringNode(InvokeVirtual instruction) {
+    private ToStringNode(InvokeMethod instruction) {
       this.instruction = instruction;
     }
 
@@ -534,7 +535,7 @@
   }
 
   /**
-   * ImplicitToStringNode are placed a StringBuilder/StringBuffer is appended to another
+   * ImplicitToStringNode are placed when StringBuilder/StringBuffer is appended to another
    * StringBuilder/StringBuffer.
    */
   static class ImplicitToStringNode extends StringBuilderNode {
@@ -588,7 +589,7 @@
     return new AppendNode(instruction);
   }
 
-  static ToStringNode createToStringNode(InvokeVirtual instruction) {
+  static ToStringNode createToStringNode(InvokeMethod instruction) {
     return new ToStringNode(instruction);
   }
 
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 dd49ae4..d7d7f59 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
@@ -61,7 +61,8 @@
             || isStringBuildingMethod(factory.stringBufferMethods, invokedMethod)) {
           return true;
         }
-        return invokedMethod == factory.objectMembers.toString
+        return (invokedMethod == factory.objectMembers.toString
+                || invokedMethod == factory.objectsMethods.toStringWithObject)
             && isLiveStringBuilder.test(instruction.getFirstOperand());
       }
       return false;
@@ -87,17 +88,20 @@
 
     @Override
     public boolean isToString(Instruction instruction, Value value) {
-      if (!instruction.isInvokeVirtual()) {
+      if (!instruction.isInvokeMethod()) {
         return false;
       }
-      InvokeVirtual invoke = instruction.asInvokeVirtual();
-      if (invoke.getReceiver() != value) {
+      if (instruction.inValues().isEmpty()) {
         return false;
       }
-      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (instruction.getFirstOperand() != value) {
+        return false;
+      }
+      DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
       return factory.stringBuilderMethods.toString == invokedMethod
           || factory.stringBufferMethods.toString == invokedMethod
-          || factory.objectMembers.toString == invokedMethod;
+          || factory.objectMembers.toString == invokedMethod
+          || factory.objectsMethods.toStringWithObject == invokedMethod;
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java
index 9b7594b..4013021 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithObjectsToStringTest.java
@@ -42,9 +42,9 @@
             inspector -> {
               MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
               assertThat(mainMethodSubject, isPresent());
-              // TODO(b/219455761): Extend StringBuilder optimizer to Objects.toString().
+              // TODO(b/114002137): Also run for CF
               assertEquals(
-                  canUseJavaUtilObjects(parameters),
+                  parameters.isCfRuntime(),
                   mainMethodSubject
                       .streamInstructions()
                       .anyMatch(InstructionSubject::isNewInstance));