Split EnumUnboxingRewriter into methods

Change-Id: I03b2b0878625454646cccdcffaa6d274dd1aa4a8
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 06816b4..d472a97 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
@@ -151,7 +151,6 @@
       return Sets.newIdentityHashSet();
     }
     assert code.isConsistentSSABeforeTypesAreCorrect(appView);
-    ProgramMethod context = code.context();
     EnumUnboxerMethodProcessorEventConsumer eventConsumer = methodProcessor.getEventConsumer();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     Map<Instruction, DexType> convertedEnums =
@@ -178,256 +177,42 @@
           if (enumType != null) {
             iterator.removeOrReplaceByDebugLocalRead();
           }
-          continue;
-        }
-
-        if (instruction.isIf()) {
-          If ifInstruction = instruction.asIf();
-          if (!ifInstruction.isZeroTest()) {
-            for (int operandIndex = 0; operandIndex < 2; operandIndex++) {
-              Value operand = ifInstruction.getOperand(operandIndex);
-              DexType enumType = getEnumClassTypeOrNull(operand, convertedEnums);
-              if (enumType != null) {
-                int otherOperandIndex = 1 - operandIndex;
-                Value otherOperand = ifInstruction.getOperand(otherOperandIndex);
-                if (otherOperand.getType().isNullType()) {
-                  iterator.previous();
-                  ifInstruction.replaceValue(
-                      otherOperandIndex, iterator.insertConstIntInstruction(code, options, 0));
-                  iterator.next();
-                  break;
-                }
-              }
-            }
-          }
-        }
-
-        // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
-        // counterpart. The rewriting (== or match) is based on the following:
-        // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
-        // - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
-        // - getClass is final and implemented only in java.lang.Object,
-        // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
-        //   also in the unboxed enum class.
-        if (instruction.isInvokeMethodWithReceiver()) {
-          InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
-          DexType enumType = getEnumClassTypeOrNull(invoke.getReceiver(), convertedEnums);
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-          if (enumType != null) {
-            if (invokedMethod == factory.enumMembers.ordinalMethod
-                || invokedMethod.match(factory.enumMembers.hashCode)) {
-              replaceEnumInvoke(
-                  iterator,
-                  invoke,
-                  getSharedUtilityClass().ensureOrdinalMethod(appView, context, eventConsumer));
-              continue;
-            } else if (invokedMethod.match(factory.enumMembers.equals)) {
-              replaceEnumInvoke(
-                  iterator,
-                  invoke,
-                  getSharedUtilityClass().ensureEqualsMethod(appView, context, eventConsumer));
-              continue;
-            } else if (invokedMethod == factory.enumMembers.compareTo
-                || invokedMethod == factory.enumMembers.compareToWithObject) {
-              replaceEnumInvoke(
-                  iterator,
-                  invoke,
-                  getSharedUtilityClass().ensureCompareToMethod(appView, context, eventConsumer));
-              continue;
-            } else if (invokedMethod == factory.enumMembers.nameMethod) {
-              rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
-              continue;
-            } else if (invokedMethod.match(factory.enumMembers.toString)) {
-              DexMethod reboundMethod =
-                  invokedMethod.withHolder(unboxedEnumsData.representativeType(enumType), factory);
-              DexMethod lookupMethod =
-                  enumUnboxingLens
-                      .lookupMethod(
-                          reboundMethod,
-                          context.getReference(),
-                          invoke.getType(),
-                          enumUnboxingLens.getPrevious())
-                      .getReference();
-              // If the SuperEnum had declared a toString() override, then the unboxer moves it to
-              // the local utility class method corresponding to that override.
-              // If a SubEnum had declared a toString() override, then the unboxer records a
-              // synthetic move from SuperEnum.toString() to the dispatch method on the local
-              // utility class.
-              // When they are the same, then there are no overrides of toString().
-              if (lookupMethod == reboundMethod) {
-                rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
-              } else {
-                DexClassAndMethod dexClassAndMethod = appView.definitionFor(lookupMethod);
-                assert dexClassAndMethod != null;
-                assert dexClassAndMethod.isProgramMethod();
-                replaceEnumInvoke(iterator, invoke, dexClassAndMethod.asProgramMethod());
-              }
-              continue;
-            } else if (invokedMethod == factory.objectMembers.getClass) {
-              rewriteNullCheck(iterator, invoke, context, eventConsumer);
-              continue;
-            } else if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
-              DexMethod refinedDispatchMethodReference =
-                  enumUnboxingLens.lookupRefinedDispatchMethod(
-                      invokedMethod,
-                      context.getReference(),
-                      invoke.getType(),
-                      enumUnboxingLens.getPrevious(),
-                      invoke.getArgument(0).getAbstractValue(appView, context),
-                      enumType);
-              if (refinedDispatchMethodReference != null) {
-                DexClassAndMethod refinedDispatchMethod =
-                    appView.definitionFor(refinedDispatchMethodReference);
-                assert refinedDispatchMethod != null;
-                assert refinedDispatchMethod.isProgramMethod();
-                replaceEnumInvoke(iterator, invoke, refinedDispatchMethod.asProgramMethod());
-              }
-              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 = invoke.getArgument(1);
-            DexType enumArgType = getEnumClassTypeOrNull(enumArg, convertedEnums);
-            if (enumArgType != null) {
-              ProgramMethod stringValueOfMethod =
-                  getLocalUtilityClass(enumArgType)
-                      .ensureStringValueOfMethod(appView, context, eventConsumer);
-              InvokeStatic toStringInvoke =
-                  InvokeStatic.builder()
-                      .setMethod(stringValueOfMethod)
-                      .setSingleArgument(enumArg)
-                      .setFreshOutValue(appView, code)
-                      .setPosition(invoke)
-                      .build();
-              DexMethod newAppendMethod =
-                  invokedMethod == factory.stringBuilderMethods.appendObject
-                      ? factory.stringBuilderMethods.appendString
-                      : factory.stringBufferMethods.appendString;
-              List<Value> arguments =
-                  ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
-              InvokeVirtual invokeAppendString =
-                  new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
-              invokeAppendString.setPosition(invoke.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.isIf()) {
+          rewriteIf(code, convertedEnums, iterator, instruction.asIf());
+        } else if (instruction.isInvokeMethodWithReceiver()) {
+          rewriteInvokeMethodWithReceiver(
+              code,
+              eventConsumer,
+              convertedEnums,
+              blocks,
+              block,
+              iterator,
+              instruction.asInvokeMethodWithReceiver());
         } else if (instruction.isInvokeStatic()) {
           rewriteInvokeStatic(
               instruction.asInvokeStatic(),
               code,
-              context,
               convertedEnums,
               iterator,
               affectedPhis,
               eventConsumer);
-        }
-        if (instruction.isStaticGet()) {
-          StaticGet staticGet = instruction.asStaticGet();
-          DexField field = staticGet.getField();
-          DexType holder = field.holder;
-          if (!unboxedEnumsData.isUnboxedEnum(holder)) {
-            continue;
-          }
-          if (staticGet.hasUnusedOutValue()) {
-            iterator.removeOrReplaceByDebugLocalRead();
-            continue;
-          }
-          affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
-          if (unboxedEnumsData.matchesValuesField(field)) {
-            // Load the size of this enum's $VALUES array before the current instruction.
-            iterator.previous();
-            Value sizeValue =
-                iterator.insertConstIntInstruction(
-                    code, options, unboxedEnumsData.getValuesSize(holder));
-            iterator.next();
-
-            // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
-            InvokeStatic invoke =
-                InvokeStatic.builder()
-                    .setMethod(getSharedUtilityClass().getValuesMethod(context, eventConsumer))
-                    .setFreshOutValue(appView, code)
-                    .setSingleArgument(sizeValue)
-                    .build();
-            iterator.replaceCurrentInstruction(invoke);
-
-            convertedEnums.put(invoke, holder);
-
-            // Check if the call to SharedUtilityClass.values(size) is followed by a call to
-            // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
-            // array. This is needed because the javac generated implementation of MyEnum.values()
-            // is implemented as `return $VALUES.clone()`.
-            removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
-          } else if (unboxedEnumsData.hasUnboxedValueFor(field)) {
-            // Replace by ordinal + 1 for null check (null is 0).
-            ConstNumber intConstant =
-                code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
-            iterator.replaceCurrentInstruction(intConstant);
-            convertedEnums.put(intConstant, holder);
-          } else {
-            // Nothing to do, handled by lens code rewriting.
-          }
-        }
-
-        if (instruction.isInstanceGet()) {
-          InstanceGet instanceGet = instruction.asInstanceGet();
-          DexType holder = instanceGet.getField().holder;
-          if (unboxedEnumsData.isUnboxedEnum(holder)) {
-            ProgramMethod fieldMethod =
-                ensureInstanceFieldMethod(instanceGet.getField(), context, eventConsumer);
-            Value rewrittenOutValue =
-                code.createValue(
-                    TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
-            Value in = instanceGet.object();
-            if (in.getType().isNullType()) {
-              iterator.previous();
-              in = iterator.insertConstIntInstruction(code, options, 0);
-              iterator.next();
-            }
-            InvokeStatic invoke =
-                new InvokeStatic(
-                    fieldMethod.getReference(), rewrittenOutValue, ImmutableList.of(in));
-            iterator.replaceCurrentInstruction(invoke);
-            if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
-              convertedEnums.put(invoke, instanceGet.getField().type);
-            }
-          }
-        }
-
-        // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
-        if (instruction.isArrayAccess()) {
-          ArrayAccess arrayAccess = instruction.asArrayAccess();
-          DexType enumType = getEnumArrayTypeOrNull(arrayAccess, convertedEnums);
-          if (enumType != null) {
-            if (arrayAccess.hasOutValue()) {
-              affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
-            }
-            arrayAccess = arrayAccess.withMemberType(MemberType.INT);
-            iterator.replaceCurrentInstruction(arrayAccess);
-            convertedEnums.put(arrayAccess, enumType);
-            if (arrayAccess.isArrayPut()) {
-              ArrayPut arrayPut = arrayAccess.asArrayPut();
-              if (arrayPut.value().getType().isNullType()) {
-                iterator.previous();
-                arrayPut.replacePutValue(iterator.insertConstIntInstruction(code, options, 0));
-                iterator.next();
-              }
-            }
-          }
-          assert validateArrayAccess(arrayAccess);
-        }
-
-        if (instruction.isNewUnboxedEnumInstance()) {
+        } else if (instruction.isStaticGet()) {
+          rewriteStaticGet(
+              code,
+              eventConsumer,
+              affectedPhis,
+              convertedEnums,
+              seenBlocks,
+              instructionsToRemove,
+              iterator,
+              instruction.asStaticGet());
+        } else if (instruction.isInstanceGet()) {
+          rewriteInstanceGet(
+              code, eventConsumer, convertedEnums, iterator, instruction.asInstanceGet());
+        } else if (instruction.isArrayAccess()) {
+          rewriteArrayAccess(
+              code, affectedPhis, convertedEnums, iterator, instruction.asArrayAccess());
+        } else if (instruction.isNewUnboxedEnumInstance()) {
           NewUnboxedEnumInstance newUnboxedEnumInstance = instruction.asNewUnboxedEnumInstance();
           assert unboxedEnumsData.isUnboxedEnum(newUnboxedEnumInstance.getType());
           iterator.replaceCurrentInstruction(
@@ -441,14 +226,265 @@
     return affectedPhis;
   }
 
+  private void rewriteArrayAccess(
+      IRCode code,
+      Set<Phi> affectedPhis,
+      Map<Instruction, DexType> convertedEnums,
+      InstructionListIterator iterator,
+      ArrayAccess arrayAccess) {
+    // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
+    DexType enumType = getEnumArrayTypeOrNull(arrayAccess, convertedEnums);
+    if (enumType != null) {
+      if (arrayAccess.hasOutValue()) {
+        affectedPhis.addAll(arrayAccess.outValue().uniquePhiUsers());
+      }
+      arrayAccess = arrayAccess.withMemberType(MemberType.INT);
+      iterator.replaceCurrentInstruction(arrayAccess);
+      convertedEnums.put(arrayAccess, enumType);
+      if (arrayAccess.isArrayPut()) {
+        ArrayPut arrayPut = arrayAccess.asArrayPut();
+        if (arrayPut.value().getType().isNullType()) {
+          iterator.previous();
+          arrayPut.replacePutValue(iterator.insertConstIntInstruction(code, options, 0));
+          iterator.next();
+        }
+      }
+    }
+    assert validateArrayAccess(arrayAccess);
+  }
+
+  private void rewriteIf(
+      IRCode code,
+      Map<Instruction, DexType> convertedEnums,
+      InstructionListIterator iterator,
+      If ifInstruction) {
+    if (!ifInstruction.isZeroTest()) {
+      for (int operandIndex = 0; operandIndex < 2; operandIndex++) {
+        Value operand = ifInstruction.getOperand(operandIndex);
+        DexType enumType = getEnumClassTypeOrNull(operand, convertedEnums);
+        if (enumType != null) {
+          int otherOperandIndex = 1 - operandIndex;
+          Value otherOperand = ifInstruction.getOperand(otherOperandIndex);
+          if (otherOperand.getType().isNullType()) {
+            iterator.previous();
+            ifInstruction.replaceValue(
+                otherOperandIndex, iterator.insertConstIntInstruction(code, options, 0));
+            iterator.next();
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  private void rewriteInstanceGet(
+      IRCode code,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+      Map<Instruction, DexType> convertedEnums,
+      InstructionListIterator iterator,
+      InstanceGet instanceGet) {
+    DexType holder = instanceGet.getField().holder;
+    if (unboxedEnumsData.isUnboxedEnum(holder)) {
+      ProgramMethod fieldMethod =
+          ensureInstanceFieldMethod(instanceGet.getField(), code.context(), eventConsumer);
+      Value rewrittenOutValue =
+          code.createValue(
+              TypeElement.fromDexType(fieldMethod.getReturnType(), maybeNull(), appView));
+      Value in = instanceGet.object();
+      if (in.getType().isNullType()) {
+        iterator.previous();
+        in = iterator.insertConstIntInstruction(code, options, 0);
+        iterator.next();
+      }
+      InvokeStatic invoke =
+          new InvokeStatic(fieldMethod.getReference(), rewrittenOutValue, ImmutableList.of(in));
+      iterator.replaceCurrentInstruction(invoke);
+      if (unboxedEnumsData.isUnboxedEnum(instanceGet.getField().type)) {
+        convertedEnums.put(invoke, instanceGet.getField().type);
+      }
+    }
+  }
+
+  private void rewriteStaticGet(
+      IRCode code,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+      Set<Phi> affectedPhis,
+      Map<Instruction, DexType> convertedEnums,
+      Set<BasicBlock> seenBlocks,
+      Set<Instruction> instructionsToRemove,
+      InstructionListIterator iterator,
+      StaticGet staticGet) {
+    DexField field = staticGet.getField();
+    DexType holder = field.holder;
+    if (!unboxedEnumsData.isUnboxedEnum(holder)) {
+      return;
+    }
+    if (staticGet.hasUnusedOutValue()) {
+      iterator.removeOrReplaceByDebugLocalRead();
+      return;
+    }
+    affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+    if (unboxedEnumsData.matchesValuesField(field)) {
+      // Load the size of this enum's $VALUES array before the current instruction.
+      iterator.previous();
+      Value sizeValue =
+          iterator.insertConstIntInstruction(code, options, unboxedEnumsData.getValuesSize(holder));
+      iterator.next();
+
+      // Replace Enum.$VALUES by a call to: int[] SharedUtilityClass.values(int size).
+      InvokeStatic invoke =
+          InvokeStatic.builder()
+              .setMethod(getSharedUtilityClass().getValuesMethod(code.context(), eventConsumer))
+              .setFreshOutValue(appView, code)
+              .setSingleArgument(sizeValue)
+              .build();
+      iterator.replaceCurrentInstruction(invoke);
+
+      convertedEnums.put(invoke, holder);
+
+      // Check if the call to SharedUtilityClass.values(size) is followed by a call to
+      // clone(). If so, remove it, since SharedUtilityClass.values(size) returns a fresh
+      // array. This is needed because the javac generated implementation of MyEnum.values()
+      // is implemented as `return $VALUES.clone()`.
+      removeRedundantValuesArrayCloning(invoke, instructionsToRemove, seenBlocks);
+    } else if (unboxedEnumsData.hasUnboxedValueFor(field)) {
+      // Replace by ordinal + 1 for null check (null is 0).
+      ConstNumber intConstant = code.createIntConstant(unboxedEnumsData.getUnboxedValue(field));
+      iterator.replaceCurrentInstruction(intConstant);
+      convertedEnums.put(intConstant, holder);
+    } else {
+      // Nothing to do, handled by lens code rewriting.
+    }
+  }
+
+  // Rewrites specific enum methods, such as ordinal, into their corresponding enum unboxed
+  // counterpart. The rewriting (== or match) is based on the following:
+  // - name, ordinal and compareTo are final and implemented only on java.lang.Enum,
+  // - equals, hashCode are final and implemented in java.lang.Enum and java.lang.Object,
+  // - getClass is final and implemented only in java.lang.Object,
+  // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
+  //   also in the unboxed enum class.
+  private void rewriteInvokeMethodWithReceiver(
+      IRCode code,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer,
+      Map<Instruction, DexType> convertedEnums,
+      BasicBlockIterator blocks,
+      BasicBlock block,
+      InstructionListIterator iterator,
+      InvokeMethodWithReceiver invoke) {
+    ProgramMethod context = code.context();
+    DexType enumType = getEnumClassTypeOrNull(invoke.getReceiver(), convertedEnums);
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (enumType != null) {
+      if (invokedMethod == factory.enumMembers.ordinalMethod
+          || invokedMethod.match(factory.enumMembers.hashCode)) {
+        replaceEnumInvoke(
+            iterator,
+            invoke,
+            getSharedUtilityClass().ensureOrdinalMethod(appView, context, eventConsumer));
+      } else if (invokedMethod.match(factory.enumMembers.equals)) {
+        replaceEnumInvoke(
+            iterator,
+            invoke,
+            getSharedUtilityClass().ensureEqualsMethod(appView, context, eventConsumer));
+      } else if (invokedMethod == factory.enumMembers.compareTo
+          || invokedMethod == factory.enumMembers.compareToWithObject) {
+        replaceEnumInvoke(
+            iterator,
+            invoke,
+            getSharedUtilityClass().ensureCompareToMethod(appView, context, eventConsumer));
+      } else if (invokedMethod == factory.enumMembers.nameMethod) {
+        rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
+      } else if (invokedMethod.match(factory.enumMembers.toString)) {
+        DexMethod reboundMethod =
+            invokedMethod.withHolder(unboxedEnumsData.representativeType(enumType), factory);
+        DexMethod lookupMethod =
+            enumUnboxingLens
+                .lookupMethod(
+                    reboundMethod,
+                    context.getReference(),
+                    invoke.getType(),
+                    enumUnboxingLens.getPrevious())
+                .getReference();
+        // If the SuperEnum had declared a toString() override, then the unboxer moves it to
+        // the local utility class method corresponding to that override.
+        // If a SubEnum had declared a toString() override, then the unboxer records a
+        // synthetic move from SuperEnum.toString() to the dispatch method on the local
+        // utility class.
+        // When they are the same, then there are no overrides of toString().
+        if (lookupMethod == reboundMethod) {
+          rewriteNameMethod(iterator, invoke, enumType, context, eventConsumer);
+        } else {
+          DexClassAndMethod dexClassAndMethod = appView.definitionFor(lookupMethod);
+          assert dexClassAndMethod != null;
+          assert dexClassAndMethod.isProgramMethod();
+          replaceEnumInvoke(iterator, invoke, dexClassAndMethod.asProgramMethod());
+        }
+      } else if (invokedMethod == factory.objectMembers.getClass) {
+        rewriteNullCheck(iterator, invoke, context, eventConsumer);
+      } else if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+        DexMethod refinedDispatchMethodReference =
+            enumUnboxingLens.lookupRefinedDispatchMethod(
+                invokedMethod,
+                context.getReference(),
+                invoke.getType(),
+                enumUnboxingLens.getPrevious(),
+                invoke.getArgument(0).getAbstractValue(appView, context),
+                enumType);
+        if (refinedDispatchMethodReference != null) {
+          DexClassAndMethod refinedDispatchMethod =
+              appView.definitionFor(refinedDispatchMethodReference);
+          assert refinedDispatchMethod != null;
+          assert refinedDispatchMethod.isProgramMethod();
+          replaceEnumInvoke(iterator, invoke, refinedDispatchMethod.asProgramMethod());
+        }
+      }
+    } 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 = invoke.getArgument(1);
+      DexType enumArgType = getEnumClassTypeOrNull(enumArg, convertedEnums);
+      if (enumArgType != null) {
+        ProgramMethod stringValueOfMethod =
+            getLocalUtilityClass(enumArgType)
+                .ensureStringValueOfMethod(appView, context, eventConsumer);
+        InvokeStatic toStringInvoke =
+            InvokeStatic.builder()
+                .setMethod(stringValueOfMethod)
+                .setSingleArgument(enumArg)
+                .setFreshOutValue(appView, code)
+                .setPosition(invoke)
+                .build();
+        DexMethod newAppendMethod =
+            invokedMethod == factory.stringBuilderMethods.appendObject
+                ? factory.stringBuilderMethods.appendString
+                : factory.stringBufferMethods.appendString;
+        List<Value> arguments = ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
+        InvokeVirtual invokeAppendString =
+            new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
+        invokeAppendString.setPosition(invoke.getPosition());
+        iterator.replaceCurrentInstruction(toStringInvoke);
+        if (block.hasCatchHandlers()) {
+          iterator
+              .splitCopyCatchHandlers(code, blocks, appView.options())
+              .listIterator(code)
+              .add(invokeAppendString);
+        } else {
+          iterator.add(invokeAppendString);
+        }
+      }
+    }
+  }
+
   private void rewriteInvokeStatic(
       InvokeStatic invoke,
       IRCode code,
-      ProgramMethod context,
       Map<Instruction, DexType> convertedEnums,
       InstructionListIterator instructionIterator,
       Set<Phi> affectedPhis,
       EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod context = code.context();
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
     if (singleTarget == null) {
       return;