Reland "Re-enable StringBuilder#append in Enum unboxer"

Bug: 166397278
Change-Id: I99b523740a5328ec89df2a35c0ae4015cac5de14
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 9b1e05e..e9c4127 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -1256,6 +1256,11 @@
         addRequiredNameData(enumClass);
         return Reason.ELIGIBLE;
       }
+      if (singleTargetReference == factory.stringBuilderMethods.appendObject
+          || singleTargetReference == factory.stringBufferMethods.appendObject) {
+        addRequiredNameData(enumClass);
+        return Reason.ELIGIBLE;
+      }
       if (singleTargetReference == factory.objectMembers.getClass
           && (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers())) {
         // This is a hidden null check.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 7ddfbaa..bbe0796 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -159,8 +160,8 @@
         if (instruction.isInvokeMethodWithReceiver()) {
           InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
           DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
+          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
           if (enumType != null) {
-            DexMethod invokedMethod = invokeMethod.getInvokedMethod();
             if (invokedMethod == factory.enumMembers.ordinalMethod
                 || invokedMethod.match(factory.enumMembers.hashCode)) {
               replaceEnumInvoke(
@@ -190,6 +191,42 @@
               assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
               replaceEnumInvoke(
                   iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+              continue;
+            }
+          } else if (invokedMethod == factory.stringBuilderMethods.appendObject
+              || invokedMethod == factory.stringBufferMethods.appendObject) {
+            // Rewrites stringBuilder.append(enumInstance) as if it was
+            // stringBuilder.append(String.valueOf(unboxedEnumInstance));
+            Value enumArg = invokeMethod.getArgument(1);
+            DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
+            if (enumArgType != null) {
+              DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
+              InvokeStatic toStringInvoke =
+                  InvokeStatic.builder()
+                      .setMethod(stringValueOfMethod)
+                      .setSingleArgument(enumArg)
+                      .setFreshOutValue(appView, code)
+                      .setPosition(invokeMethod)
+                      .build();
+              DexMethod newAppendMethod =
+                  invokedMethod == factory.stringBuilderMethods.appendObject
+                      ? factory.stringBuilderMethods.appendString
+                      : factory.stringBufferMethods.appendString;
+              List<Value> arguments =
+                  ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
+              InvokeVirtual invokeAppendString =
+                  new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
+              invokeAppendString.setPosition(invokeMethod.getPosition());
+              iterator.replaceCurrentInstruction(toStringInvoke);
+              if (block.hasCatchHandlers()) {
+                iterator
+                    .splitCopyCatchHandlers(code, blocks, appView.options())
+                    .listIterator(code)
+                    .add(invokeAppendString);
+              } else {
+                iterator.add(invokeAppendString);
+              }
+              continue;
             }
           }
         } else if (instruction.isInvokeStatic()) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
index 904fc48f..2211d08 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/StringValueOfEnumUnboxingTest.java
@@ -59,12 +59,76 @@
     public static void main(String[] args) {
       System.out.println(MyEnum.A.ordinal());
       System.out.println(0);
+      stringValueOf();
+      stringBuilder();
+    }
+
+    private static void stringValueOf() {
       System.out.println(getString(MyEnum.A));
       System.out.println("A");
       System.out.println(getString(null));
       System.out.println("null");
     }
 
+    private static void stringBuilder() {
+      StringBuilder stringBuilder = new StringBuilder();
+      append(stringBuilder, MyEnum.A);
+      append(stringBuilder, MyEnum.B);
+      append(stringBuilder, null);
+      appendTryCatch(stringBuilder, MyEnum.A);
+      appendTryCatch(stringBuilder, MyEnum.B);
+      appendTryCatch(stringBuilder, null);
+      System.out.println(stringBuilder.toString());
+      System.out.println("ABnullABnull");
+
+      StringBuffer stringBuffer = new StringBuffer();
+      append(stringBuffer, MyEnum.A);
+      append(stringBuffer, MyEnum.B);
+      append(stringBuffer, null);
+      appendTryCatch(stringBuffer, MyEnum.A);
+      appendTryCatch(stringBuffer, MyEnum.B);
+      appendTryCatch(stringBuffer, null);
+      System.out.println(stringBuffer.toString());
+      System.out.println("ABnullABnull");
+    }
+
+    @NeverInline
+    private static StringBuilder append(StringBuilder sb, MyEnum e) {
+      return sb.append(e);
+    }
+
+    @NeverInline
+    private static StringBuffer append(StringBuffer sb, MyEnum e) {
+      return sb.append(e);
+    }
+
+    @NeverInline
+    private static StringBuilder appendTryCatch(StringBuilder sb, MyEnum e) {
+      try {
+        sb.append(e);
+        throwNull();
+      } catch (NullPointerException ignored) {
+      }
+      return sb;
+    }
+
+    @NeverInline
+    private static StringBuffer appendTryCatch(StringBuffer sb, MyEnum e) {
+      try {
+        sb.append(e);
+        throwNull();
+      } catch (NullPointerException ignored) {
+      }
+      return sb;
+    }
+
+    @NeverInline
+    private static void throwNull() {
+      if (System.currentTimeMillis() > 0) {
+        throw new NullPointerException("exception");
+      }
+    }
+
     @NeverInline
     private static String getString(MyEnum e) {
       return String.valueOf(e);