Refactor interface instruction desugaring determination.

Fixes: 187913003
Change-Id: I29c60d07590874d8275ee02cbaa27303f24acade
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugarDescription.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugarDescription.java
new file mode 100644
index 0000000..79e6e04
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugarDescription.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2021, 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.desugar;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A description of the desugaring task.
+ *
+ * <p>The description encodes if there are any side effects that must be done as part of the
+ * desugaring checks (such as issue diagnostics) as well as if a given instruction needs to be
+ * desugared, and if so, how to desugar it. The process of computing a description should be
+ * side-effect free. All side effects should be done either in the 'scan' or the
+ * 'desugarInstruction' callbacks.
+ */
+public class DesugarDescription {
+
+  private static final DesugarDescription NOTHING = new DesugarDescription();
+
+  private DesugarDescription() {}
+
+  public void scan() {}
+
+  public boolean needsDesugaring() {
+    return false;
+  }
+
+  public Collection<CfInstruction> desugarInstruction(
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    return null;
+  }
+
+  public static DesugarDescription nothing() {
+    assert NOTHING == builder().build();
+    return NOTHING;
+  }
+
+  public static Builder builder() {
+    return InitialBuilder.getInstance();
+  }
+
+  @FunctionalInterface
+  public interface ScanCallback {
+    void scan();
+  }
+
+  @FunctionalInterface
+  public interface DesugarCallback {
+    Collection<CfInstruction> desugarInstruction(
+        FreshLocalProvider freshLocalProvider,
+        LocalStackAllocator localStackAllocator,
+        CfInstructionDesugaringEventConsumer eventConsumer,
+        ProgramMethod context,
+        MethodProcessingContext methodProcessingContext,
+        DexItemFactory dexItemFactory);
+  }
+
+  public abstract static class Builder {
+    public abstract DesugarDescription build();
+
+    public abstract Builder addScanEffect(ScanCallback callback);
+
+    public abstract Builder setDesugarRewrite(DesugarCallback callback);
+  }
+
+  /**
+   * Initial builder is an empty singleton. Any actual change will result in the allocation of a
+   * non-empty builder. This ensures that the trivial case has zero allocation overhead.
+   */
+  static class InitialBuilder extends Builder {
+    static final InitialBuilder INSTANCE = new InitialBuilder();
+
+    public static InitialBuilder getInstance() {
+      return INSTANCE;
+    }
+
+    @Override
+    public DesugarDescription build() {
+      return NOTHING;
+    }
+
+    @Override
+    public Builder addScanEffect(ScanCallback callback) {
+      return new NonEmptyBuilder().addScanEffect(callback);
+    }
+
+    @Override
+    public Builder setDesugarRewrite(DesugarCallback callback) {
+      return new NonEmptyBuilder().setDesugarRewrite(callback);
+    }
+  }
+
+  static class NonEmptyBuilder extends Builder {
+
+    List<ScanCallback> scanEffects = new ArrayList<>();
+    DesugarCallback desugarRewrite = null;
+
+    @Override
+    public Builder addScanEffect(ScanCallback callback) {
+      assert callback != null;
+      scanEffects.add(callback);
+      return this;
+    }
+
+    @Override
+    public Builder setDesugarRewrite(DesugarCallback desugarRewrite) {
+      assert this.desugarRewrite == null;
+      assert desugarRewrite != null;
+      this.desugarRewrite = desugarRewrite;
+      return this;
+    }
+
+    @Override
+    public DesugarDescription build() {
+      return new DesugarDescription() {
+        @Override
+        public void scan() {
+          scanEffects.forEach(ScanCallback::scan);
+        }
+
+        @Override
+        public boolean needsDesugaring() {
+          return desugarRewrite != null;
+        }
+
+        @Override
+        public Collection<CfInstruction> desugarInstruction(
+            FreshLocalProvider freshLocalProvider,
+            LocalStackAllocator localStackAllocator,
+            CfInstructionDesugaringEventConsumer eventConsumer,
+            ProgramMethod context,
+            MethodProcessingContext methodProcessingContext,
+            DexItemFactory dexItemFactory) {
+          return desugarRewrite == null
+              ? null
+              : desugarRewrite.desugarInstruction(
+                  freshLocalProvider,
+                  localStackAllocator,
+                  eventConsumer,
+                  context,
+                  methodProcessingContext,
+                  dexItemFactory);
+        }
+      };
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 8f3f0a3..6117eb7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -4,12 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.itf;
 
-import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
-import static com.android.tools.r8.ir.code.Invoke.Type.INTERFACE;
-import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
-import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
-import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
-
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -38,12 +32,12 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryConfiguration;
@@ -69,8 +63,6 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Predicate;
 
 //
@@ -234,36 +226,6 @@
                         + InterfaceDesugaringSyntheticHelper.COMPANION_CLASS_NAME_SUFFIX)));
   }
 
-  private boolean needsRewriting(DexMethod method, Type invokeType, ProgramMethod context) {
-    return !isSyntheticMethodThatShouldNotBeDoubleProcessed(context)
-        && invokeNeedsRewriting(method, invokeType);
-  }
-
-  private boolean invokeNeedsRewriting(DexMethod method, Type invokeType) {
-    // TODO(b/187913003): Refactor the implementation of needsDesugaring and desugarInstruction so
-    //  that the identification is shared and thus guaranteed to be equivalent.
-    if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
-      DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
-      if (clazz != null && clazz.isInterface()) {
-        return true;
-      }
-      return emulatedMethods.contains(method.getName());
-    }
-    if (invokeType == VIRTUAL || invokeType == INTERFACE) {
-      // A virtual dispatch can target a private method, on self or on a nest mate.
-      AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
-      SingleResolutionResult resolution =
-          appInfoForDesugaring.resolveMethod(method, invokeType == INTERFACE).asSingleResolution();
-      if (resolution != null
-          && (resolution.getResolvedMethod().isPrivate()
-              || resolution.getResolvedMethod().isStatic())) {
-        return true;
-      }
-      return defaultMethodForEmulatedDispatchOrNull(method, invokeType == INTERFACE) != null;
-    }
-    return true;
-  }
-
   private boolean isAlreadyDesugared(CfInvoke invoke, ProgramMethod context) {
     // In Cf to Cf it is forbidden to desugar twice the same instruction, if the backported
     // method rewriter or the desugared library retargeter already desugar the instruction, they
@@ -296,87 +258,17 @@
           && !StringConcatInstructionDesugaring.isStringConcatInvoke(
               instruction, appView.dexItemFactory())) {
         reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), context);
-        continue;
       }
-      if (instruction.isInvoke()) {
-        CfInvoke cfInvoke = instruction.asInvoke();
-        if (isAlreadyDesugared(cfInvoke, context)) {
-          continue;
-        }
-        if (cfInvoke.isInvokeStatic()) {
-          scanInvokeStatic(cfInvoke, context);
-        } else if (cfInvoke.isInvokeSpecial()) {
-          scanInvokeDirectOrSuper(cfInvoke, context);
-        }
-      }
-    }
-  }
-
-  private void scanInvokeDirectOrSuper(CfInvoke cfInvoke, ProgramMethod context) {
-    if (cfInvoke.isInvokeConstructor(factory)) {
-      return;
-    }
-    DexMethod invokedMethod = cfInvoke.getMethod();
-    DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
-    if (clazz == null) {
-      // NOTE: For invoke-super, this leaves unchanged those calls to undefined targets.
-      // This may lead to runtime exception but we can not report it as error since it can also be
-      // the intended behavior.
-      // For invoke-direct, this reports the missing class since we don't know if it is an
-      // interface.
-      warnMissingType(context, invokedMethod.holder);
-    }
-  }
-
-  private void scanInvokeStatic(CfInvoke cfInvoke, ProgramMethod context) {
-    DexMethod invokedMethod = cfInvoke.getMethod();
-    DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
-    if (clazz == null) {
-      // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
-      // exception but we can not report it as error since it can also be the intended
-      // behavior.
-      if (cfInvoke.isInterface()) {
-        leavingStaticInvokeToInterface(context);
-        warnMissingType(context, invokedMethod.holder);
-      }
-      return;
-    }
-
-    if (!clazz.isInterface()) {
-      if (cfInvoke.isInterface()) {
-        leavingStaticInvokeToInterface(context);
-      }
-      return;
-    }
-
-    if (isNonDesugaredLibraryClass(clazz)) {
-      // NOTE: we intentionally don't desugar static calls into static interface
-      // methods coming from android.jar since it is only possible in case v24+
-      // version of android.jar is provided.
-      //
-      // We assume such calls are properly guarded by if-checks like
-      //    'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
-      //
-      // WARNING: This may result in incorrect code on older platforms!
-      // Retarget call to an appropriate method of companion class.
-
-      if (options.canLeaveStaticInterfaceMethodInvokes()) {
-        // When leaving static interface method invokes upgrade the class file version.
-        leavingStaticInvokeToInterface(context);
-      }
+      computeDescription(instruction, context).scan();
     }
   }
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    if (instruction.isInvoke()) {
-      CfInvoke cfInvoke = instruction.asInvoke();
-      if (isAlreadyDesugared(cfInvoke, context)) {
-        return false;
-      }
-      return needsRewriting(cfInvoke.getMethod(), cfInvoke.getInvokeType(context), context);
+    if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
+      return false;
     }
-    return false;
+    return computeDescription(instruction, context).needsDesugaring();
   }
 
   @Override
@@ -388,83 +280,333 @@
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext,
       DexItemFactory dexItemFactory) {
-    if (!instruction.isInvoke() || isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
-      return null;
-    }
+    assert !isSyntheticMethodThatShouldNotBeDoubleProcessed(context);
+    return computeDescription(instruction, context)
+        .desugarInstruction(
+            freshLocalProvider,
+            localStackAllocator,
+            eventConsumer,
+            context,
+            methodProcessingContext,
+            dexItemFactory);
+  }
+
+  private DesugarDescription computeDescription(CfInstruction instruction, ProgramMethod context) {
     CfInvoke invoke = instruction.asInvoke();
-    if (isAlreadyDesugared(invoke, context)) {
-      return null;
+    if (invoke == null || isAlreadyDesugared(invoke, context)) {
+      return DesugarDescription.nothing();
+    }
+    if (invoke.isInvokeStatic()) {
+      return computeInvokeStatic(invoke, context);
+    }
+    if (invoke.isInvokeSpecial()) {
+      return computeInvokeSpecial(invoke, context);
+    }
+    if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+      return computeInvokeVirtualDispatch(invoke, context);
+    }
+    return DesugarDescription.nothing();
+  }
+
+  private DesugarDescription computeInvokeSpecial(CfInvoke invoke, ProgramMethod context) {
+    if (invoke.isInvokeConstructor(factory)) {
+      return DesugarDescription.nothing();
+    }
+    DexClass holder = appView.definitionForHolder(invoke.getMethod(), context);
+    if (holder == null) {
+      // NOTE: For invoke-super, this leaves unchanged those calls to undefined targets.
+      // This may lead to runtime exception but we can not report it as error since it can also be
+      // the intended behavior.
+      // For invoke-direct, this reports the missing class since we don't know if it is an
+      // interface.
+      return DesugarDescription.builder()
+          .addScanEffect(() -> warnMissingType(context, invoke.getMethod().getHolderType()))
+          .build();
+    }
+    if (invoke.isInvokeSuper(context.getHolderType())) {
+      return rewriteInvokeSuper(invoke, context);
+    }
+    return computeInvokeDirect(invoke, context);
+  }
+
+  private DesugarDescription computeInvokeStatic(CfInvoke invoke, ProgramMethod context) {
+    DexClass holder = appView.definitionForHolder(invoke.getMethod(), context);
+    if (holder == null) {
+      // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
+      // exception but we can not report it as error since it can also be the intended
+      // behavior.
+      if (invoke.isInterface()) {
+        return DesugarDescription.builder()
+            .addScanEffect(
+                () -> {
+                  leavingStaticInvokeToInterface(context);
+                  warnMissingType(context, invoke.getMethod().getHolderType());
+                })
+            .build();
+      }
+      return DesugarDescription.nothing();
+    }
+    if (!holder.isInterface()) {
+      if (invoke.isInterface()) {
+        return DesugarDescription.builder()
+            .addScanEffect(() -> leavingStaticInvokeToInterface(context))
+            .build();
+      }
+      return DesugarDescription.nothing();
+    }
+    // TODO(b/183998768): This should not be needed. Targeted synthetics should be in place.
+    if (appView.getSyntheticItems().isPendingSynthetic(invoke.getMethod().getHolderType())) {
+      // We did not create this code yet, but it will not require rewriting.
+      return DesugarDescription.nothing();
+    }
+    if (isNonDesugaredLibraryClass(holder)) {
+      // NOTE: we intentionally don't desugar static calls into static interface
+      // methods coming from android.jar since it is only possible in case v24+
+      // version of android.jar is provided.
+      //
+      // We assume such calls are properly guarded by if-checks like
+      //    'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
+      //
+      // WARNING: This may result in incorrect code on older platforms!
+      // Retarget call to an appropriate method of companion class.
+      if (options.canLeaveStaticInterfaceMethodInvokes()) {
+        // When leaving static interface method invokes upgrade the class file version.
+        return DesugarDescription.builder()
+            .addScanEffect(() -> leavingStaticInvokeToInterface(context))
+            .build();
+      }
+      // On pre-L devices static calls to interface methods result in verifier
+      // rejecting the whole class. We have to create special dispatch classes,
+      // so the user class is not rejected because it make this call directly.
+      // TODO(b/166247515): If this an incorrect invoke-static without the interface bit
+      //  we end up "fixing" the code and remove and ICCE error.
+      if (synthesizedMethods.contains(context)) {
+        // When reprocessing the method generated below, the desugaring asserts this method
+        // does not need any new desugaring, while the interface method rewriter tries
+        // to outline again the invoke-static. Just do nothing instead.
+        return DesugarDescription.nothing();
+      }
+      return DesugarDescription.builder()
+          .setDesugarRewrite(
+              (freshLocalProvider,
+                  localStackAllocator,
+                  eventConsumer,
+                  context1,
+                  methodProcessingContext,
+                  dexItemFactory) -> {
+                ProgramMethod newProgramMethod =
+                    appView
+                        .getSyntheticItems()
+                        .createMethod(
+                            SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
+                            methodProcessingContext.createUniqueContext(),
+                            appView,
+                            syntheticMethodBuilder ->
+                                syntheticMethodBuilder
+                                    .setProto(invoke.getMethod().getProto())
+                                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                                    .setCode(
+                                        m ->
+                                            ForwardMethodBuilder.builder(factory)
+                                                .setStaticTarget(invoke.getMethod(), true)
+                                                .setStaticSource(m)
+                                                .build()));
+                synthesizedMethods.add(newProgramMethod);
+                eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
+                    newProgramMethod, context1);
+                // The synthetic dispatch class has static interface method invokes, so set
+                // the class file version accordingly.
+                leavingStaticInvokeToInterface(newProgramMethod);
+                return getInvokeStaticInstructions(newProgramMethod.getReference());
+              })
+          .build();
     }
 
-    Function<DexMethod, Collection<CfInstruction>> rewriteInvoke =
-        (newTarget) ->
-            Collections.singletonList(
-                new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, newTarget, false));
+    SingleResolutionResult resolutionResult =
+        appView
+            .appInfoForDesugaring()
+            .resolveMethodOnInterface(holder, invoke.getMethod())
+            .asSingleResolution();
+    if (holder.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
+      return computeInvokeAsThrowRewrite(invoke, resolutionResult);
+    }
 
-    Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
-        (resolutionResult) ->
-            rewriteInvokeToThrowCf(
-                invoke,
-                resolutionResult,
+    assert resolutionResult != null;
+    assert resolutionResult.getResolvedMethod().isStatic();
+    DexClassAndMethod method = resolutionResult.getResolutionPair();
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (freshLocalProvider,
+                localStackAllocator,
+                eventConsumer,
+                context12,
+                methodProcessingContext,
+                dexItemFactory) -> {
+              DexClassAndMethod companionMethod =
+                  helper.ensureStaticAsMethodOfCompanionClassStub(method, eventConsumer);
+              return getInvokeStaticInstructions(companionMethod.getReference());
+            })
+        .build();
+  }
+
+  private DesugarDescription computeInvokeVirtualDispatch(CfInvoke invoke, ProgramMethod context) {
+    AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+    SingleResolutionResult resolution =
+        appInfoForDesugaring
+            .resolveMethod(invoke.getMethod(), invoke.isInterface())
+            .asSingleResolution();
+    if (resolution != null
+        && resolution.getResolvedMethod().isPrivate()
+        && resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
+      // TODO(b/198267586): What about the private in-accessible case?
+      return computeInvokeDirect(invoke, context);
+    }
+    if (resolution != null && resolution.getResolvedMethod().isStatic()) {
+      return computeInvokeAsThrowRewrite(invoke, resolution);
+    }
+    // TODO(b/198267586): What about an invoke <init>?
+    DexClassAndMethod defaultMethod =
+        defaultMethodForEmulatedDispatchOrNull(invoke.getMethod(), invoke.isInterface());
+    if (defaultMethod != null) {
+      return DesugarDescription.builder()
+          .setDesugarRewrite(
+              (freshLocalProvider,
+                  localStackAllocator,
+                  eventConsumer,
+                  context1,
+                  methodProcessingContext,
+                  dexItemFactory) ->
+                  getInvokeStaticInstructions(
+                      helper
+                          .ensureEmulatedInterfaceMethod(defaultMethod, eventConsumer)
+                          .getReference()))
+          .build();
+    }
+    return DesugarDescription.nothing();
+  }
+
+  private DesugarDescription computeInvokeDirect(CfInvoke invoke, ProgramMethod context) {
+    if (invoke.isInvokeConstructor(factory)) {
+      return DesugarDescription.nothing();
+    }
+    DexMethod invokedMethod = invoke.getMethod();
+    DexClass clazz = appView.definitionForHolder(invokedMethod, context);
+    if (clazz == null) {
+      // Report missing class since we don't know if it is an interface.
+      return DesugarDescription.builder()
+          .addScanEffect(() -> warnMissingType(context, invokedMethod.getHolderType()))
+          .build();
+    }
+    if (!clazz.isInterface()) {
+      return DesugarDescription.nothing();
+    }
+
+    if (clazz.isLibraryClass()) {
+      throw new CompilationError(
+          "Unexpected call to a private method "
+              + "defined in library class "
+              + clazz.toSourceString(),
+          getMethodOrigin(context.getReference()));
+    }
+
+    DexClassAndMethod directTarget = clazz.lookupClassMethod(invokedMethod);
+    if (directTarget != null) {
+      return DesugarDescription.builder()
+          .setDesugarRewrite(
+              (freshLocalProvider,
+                  localStackAllocator,
+                  eventConsumer,
+                  context1,
+                  methodProcessingContext,
+                  dexItemFactory) -> {
+                // This can be a private instance method call. Note that the referenced
+                // method is expected to be in the current class since it is private, but desugaring
+                // may move some methods or their code into other classes.
+                DexClassAndMethod companionMethodDefinition = null;
+                DexMethod companionMethod;
+                if (directTarget.getDefinition().isPrivateMethod()) {
+                  if (directTarget.isProgramMethod()) {
+                    companionMethodDefinition =
+                        helper.ensurePrivateAsMethodOfProgramCompanionClassStub(
+                            directTarget.asProgramMethod());
+                    companionMethod = companionMethodDefinition.getReference();
+                  } else {
+                    // TODO(b/183998768): Why does this not create a stub on the class path?
+                    companionMethod = helper.privateAsMethodOfCompanionClass(directTarget);
+                  }
+                } else {
+                  companionMethodDefinition =
+                      helper.ensureDefaultAsMethodOfCompanionClassStub(directTarget);
+                  companionMethod = companionMethodDefinition.getReference();
+                }
+                if (companionMethodDefinition != null) {
+                  acceptCompanionMethod(directTarget, companionMethodDefinition, eventConsumer);
+                }
+                return getInvokeStaticInstructions(companionMethod);
+              })
+          .build();
+    } else {
+      // The method can be a default method in the interface hierarchy.
+      DexClassAndMethod virtualTarget =
+          appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, invokedMethod);
+      if (virtualTarget != null) {
+        return DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context12,
+                    methodProcessingContext,
+                    dexItemFactory) -> {
+                  // This is a invoke-direct call to a virtual method.
+                  DexClassAndMethod companionMethod =
+                      helper.ensureDefaultAsMethodOfCompanionClassStub(virtualTarget);
+                  acceptCompanionMethod(virtualTarget, companionMethod, eventConsumer);
+                  return getInvokeStaticInstructions(companionMethod.getReference());
+                })
+            .build();
+      } else {
+        // The below assert is here because a well-type program should have a target, but we
+        // cannot throw a compilation error, since we have no knowledge about the input.
+        assert false;
+      }
+    }
+    return DesugarDescription.nothing();
+  }
+
+  private DesugarDescription computeInvokeAsThrowRewrite(
+      CfInvoke invoke, SingleResolutionResult resolution) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (freshLocalProvider,
                 localStackAllocator,
                 eventConsumer,
                 context,
-                methodProcessingContext);
-
-    if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
-      AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
-      SingleResolutionResult resolution =
-          appInfoForDesugaring
-              .resolveMethod(invoke.getMethod(), invoke.isInterface())
-              .asSingleResolution();
-      if (resolution != null
-          && resolution.getResolvedMethod().isPrivate()
-          && resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
-        // TODO(b/198267586): What about the private in-accessible case?
-        return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke, eventConsumer);
-      }
-      if (resolution != null && resolution.getResolvedMethod().isStatic()) {
-        return rewriteToThrow.apply(resolution);
-      }
-      // TODO(b/198267586): What about an invoke <init>?
-      return rewriteInvokeInterfaceOrInvokeVirtual(
-          invoke.getMethod(), invoke.isInterface(), rewriteInvoke, eventConsumer);
-    }
-    if (invoke.isInvokeStatic()) {
-      Consumer<ProgramMethod> staticOutliningMethodConsumer =
-          staticOutliningMethod -> {
-            synthesizedMethods.add(staticOutliningMethod);
-            eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
-                staticOutliningMethod, context);
-          };
-      // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
-      return rewriteInvokeStatic(
-          invoke,
-          methodProcessingContext,
-          context,
-          staticOutliningMethodConsumer,
-          rewriteInvoke,
-          rewriteToThrow,
-          eventConsumer);
-    }
-    assert invoke.isInvokeSpecial();
-    if (invoke.isInvokeSuper(context.getHolderType())) {
-      return rewriteInvokeSuper(
-          invoke.getMethod(), context, rewriteInvoke, rewriteToThrow, eventConsumer);
-    }
-    return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke, eventConsumer);
+                methodProcessingContext,
+                dexItemFactory) ->
+                getThrowInstructions(
+                    invoke,
+                    resolution,
+                    localStackAllocator,
+                    eventConsumer,
+                    context,
+                    methodProcessingContext))
+        .build();
   }
 
-  private Collection<CfInstruction> rewriteInvokeToThrowCf(
+  private Collection<CfInstruction> getInvokeStaticInstructions(DexMethod newTarget) {
+    return Collections.singletonList(
+        new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, newTarget, false));
+  }
+
+  private Collection<CfInstruction> getThrowInstructions(
       CfInvoke invoke,
       SingleResolutionResult resolutionResult,
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-    if (isAlreadyDesugared(invoke, context)) {
-      return null;
-    }
+    assert !isAlreadyDesugared(invoke, context);
 
     MethodSynthesizerConsumer methodSynthesizerConsumer;
     if (resolutionResult == null) {
@@ -478,8 +620,6 @@
       return null;
     }
 
-    assert needsDesugaring(invoke, context);
-
     // Replace the entire effect of the invoke by by call to the throwing helper:
     //   ...
     //   invoke <method> [receiver] args*
@@ -560,80 +700,6 @@
     }
   }
 
-  private Collection<CfInstruction> rewriteInvokeDirect(
-      DexMethod invokedMethod,
-      ProgramMethod context,
-      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
-      InterfaceMethodDesugaringEventConsumer eventConsumer) {
-    if (factory.isConstructor(invokedMethod)) {
-      return null;
-    }
-
-    DexClass clazz = appView.definitionForHolder(invokedMethod, context);
-    if (clazz == null) {
-      // Report missing class since we don't know if it is an interface.
-      warnMissingType(context, invokedMethod.holder);
-      return null;
-    }
-
-    if (!clazz.isInterface()) {
-      return null;
-    }
-
-    if (clazz.isLibraryClass()) {
-      throw new CompilationError(
-          "Unexpected call to a private method "
-              + "defined in library class "
-              + clazz.toSourceString(),
-          getMethodOrigin(context.getReference()));
-    }
-
-    DexClassAndMethod directTarget = clazz.lookupClassMethod(invokedMethod);
-    if (directTarget != null) {
-      // This can be a private instance method call. Note that the referenced
-      // method is expected to be in the current class since it is private, but desugaring
-      // may move some methods or their code into other classes.
-      assert invokeNeedsRewriting(invokedMethod, DIRECT);
-      DexClassAndMethod companionMethodDefinition = null;
-      DexMethod companionMethod;
-      if (directTarget.getDefinition().isPrivateMethod()) {
-        if (directTarget.isProgramMethod()) {
-          companionMethodDefinition =
-              helper.ensurePrivateAsMethodOfProgramCompanionClassStub(
-                  directTarget.asProgramMethod());
-          companionMethod = companionMethodDefinition.getReference();
-        } else {
-          // TODO(b/183998768): Why does this not create a stub on the class path?
-          companionMethod = helper.privateAsMethodOfCompanionClass(directTarget);
-        }
-      } else {
-        companionMethodDefinition = helper.ensureDefaultAsMethodOfCompanionClassStub(directTarget);
-        companionMethod = companionMethodDefinition.getReference();
-      }
-      if (companionMethodDefinition != null) {
-        acceptCompanionMethod(directTarget, companionMethodDefinition, eventConsumer);
-      }
-      return rewriteInvoke.apply(companionMethod);
-    } else {
-      // The method can be a default method in the interface hierarchy.
-      DexClassAndMethod virtualTarget =
-          appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, invokedMethod);
-      if (virtualTarget != null) {
-        // This is a invoke-direct call to a virtual method.
-        assert invokeNeedsRewriting(invokedMethod, DIRECT);
-        DexClassAndMethod companionMethod =
-            helper.ensureDefaultAsMethodOfCompanionClassStub(virtualTarget);
-        acceptCompanionMethod(virtualTarget, companionMethod, eventConsumer);
-        return rewriteInvoke.apply(companionMethod.getReference());
-      } else {
-        // The below assert is here because a well-type program should have a target, but we
-        // cannot throw a compilation error, since we have no knowledge about the input.
-        assert false;
-      }
-    }
-    return null;
-  }
-
   private void acceptCompanionMethod(
       DexClassAndMethod method,
       DexClassAndMethod companion,
@@ -644,137 +710,22 @@
     }
   }
 
-  private Collection<CfInstruction> rewriteInvokeStatic(
-      CfInvoke invoke,
-      MethodProcessingContext methodProcessingContext,
-      ProgramMethod context,
-      Consumer<ProgramMethod> staticOutliningMethodConsumer,
-      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
-      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow,
-      CfInstructionDesugaringEventConsumer eventConsumer) {
+  private DesugarDescription rewriteInvokeSuper(CfInvoke invoke, ProgramMethod context) {
     DexMethod invokedMethod = invoke.getMethod();
-    boolean interfaceBit = invoke.isInterface();
-    if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
-      // We did not create this code yet, but it will not require rewriting.
-      return null;
-    }
-
     DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
     if (clazz == null) {
       // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
       // exception but we can not report it as error since it can also be the intended
       // behavior.
-      if (interfaceBit) {
-        leavingStaticInvokeToInterface(context);
-      }
-      warnMissingType(context, invokedMethod.holder);
-      return null;
-    }
-
-    if (!clazz.isInterface()) {
-      if (interfaceBit) {
-        leavingStaticInvokeToInterface(context);
-      }
-      return null;
-    }
-
-    if (isNonDesugaredLibraryClass(clazz)) {
-      // NOTE: we intentionally don't desugar static calls into static interface
-      // methods coming from android.jar since it is only possible in case v24+
-      // version of android.jar is provided.
-      //
-      // We assume such calls are properly guarded by if-checks like
-      //    'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
-      //
-      // WARNING: This may result in incorrect code on older platforms!
-      // Retarget call to an appropriate method of companion class.
-
-      if (!options.canLeaveStaticInterfaceMethodInvokes()) {
-        // On pre-L devices static calls to interface methods result in verifier
-        // rejecting the whole class. We have to create special dispatch classes,
-        // so the user class is not rejected because it make this call directly.
-        // TODO(b/166247515): If this an incorrect invoke-static without the interface bit
-        //  we end up "fixing" the code and remove and ICCE error.
-        if (synthesizedMethods.contains(context)) {
-          // When reprocessing the method generated below, the desugaring asserts this method
-          // does not need any new desugaring, while the interface method rewriter tries
-          // to outline again the invoke-static. Just do nothing instead.
-          return null;
-        }
-        if (isAlreadyDesugared(invoke, context)) {
-          return null;
-        }
-        ProgramMethod newProgramMethod =
-            appView
-                .getSyntheticItems()
-                .createMethod(
-                    SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
-                    methodProcessingContext.createUniqueContext(),
-                    appView,
-                    syntheticMethodBuilder ->
-                        syntheticMethodBuilder
-                            .setProto(invokedMethod.proto)
-                            .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                            // Will be traced by the enqueuer.
-                            .disableAndroidApiLevelCheck()
-                            .setCode(
-                                m ->
-                                    ForwardMethodBuilder.builder(factory)
-                                        .setStaticTarget(invokedMethod, true)
-                                        .setStaticSource(m)
-                                        .build()));
-        staticOutliningMethodConsumer.accept(newProgramMethod);
-        assert invokeNeedsRewriting(invokedMethod, STATIC);
-        // The synthetic dispatch class has static interface method invokes, so set
-        // the class file version accordingly.
-        leavingStaticInvokeToInterface(newProgramMethod);
-        return rewriteInvoke.apply(newProgramMethod.getReference());
-      } else {
-        // When leaving static interface method invokes upgrade the class file version.
-        leavingStaticInvokeToInterface(context);
-      }
-      return null;
-    }
-
-    SingleResolutionResult resolutionResult =
-        appView
-            .appInfoForDesugaring()
-            .resolveMethodOnInterface(clazz, invokedMethod)
-            .asSingleResolution();
-    if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
-      assert invokeNeedsRewriting(invokedMethod, STATIC);
-      return rewriteToThrow.apply(resolutionResult);
-    }
-
-    assert resolutionResult != null;
-    assert resolutionResult.getResolvedMethod().isStatic();
-    assert invokeNeedsRewriting(invokedMethod, STATIC);
-    DexClassAndMethod method = resolutionResult.getResolutionPair();
-    DexClassAndMethod companionMethod =
-        helper.ensureStaticAsMethodOfCompanionClassStub(method, eventConsumer);
-    return rewriteInvoke.apply(companionMethod.getReference());
-  }
-
-  private Collection<CfInstruction> rewriteInvokeSuper(
-      DexMethod invokedMethod,
-      ProgramMethod context,
-      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
-      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow,
-      InterfaceMethodDesugaringEventConsumer eventConsumer) {
-    DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
-    if (clazz == null) {
-      // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
-      // exception but we can not report it as error since it can also be the intended
-      // behavior.
-      warnMissingType(context, invokedMethod.holder);
-      return null;
+      return DesugarDescription.builder()
+          .addScanEffect(() -> warnMissingType(context, invokedMethod.holder))
+          .build();
     }
 
     SingleResolutionResult resolutionResult =
         appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
     if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
-      assert invokeNeedsRewriting(invokedMethod, SUPER);
-      return rewriteToThrow.apply(resolutionResult);
+      return computeInvokeAsThrowRewrite(invoke, resolutionResult);
     }
 
     if (clazz.isInterface() && !clazz.isLibraryClass()) {
@@ -787,36 +738,60 @@
       //
       // WARNING: This may result in incorrect code on older platforms!
       // Retarget call to an appropriate method of companion class.
-      assert invokeNeedsRewriting(invokedMethod, SUPER);
       if (resolutionResult.getResolvedMethod().isPrivateMethod()) {
         if (resolutionResult.isAccessibleFrom(context, appView.appInfoForDesugaring()).isFalse()) {
           // TODO(b/145775365): This should throw IAE.
-          return rewriteToThrow.apply(null);
+          return computeInvokeAsThrowRewrite(invoke, null);
         }
-        DexClassAndMethod method = resolutionResult.getResolutionPair();
-        DexMethod companionMethod;
-        if (method.isProgramMethod()) {
-          ProgramMethod companionMethodDefinition =
-              helper.ensurePrivateAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
-          companionMethod = companionMethodDefinition.getReference();
-          eventConsumer.acceptCompanionMethod(method.asProgramMethod(), companionMethodDefinition);
-        } else {
-          companionMethod = helper.privateAsMethodOfCompanionClass(method);
-        }
-        return rewriteInvoke.apply(companionMethod);
+        return DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context1,
+                    methodProcessingContext,
+                    dexItemFactory) -> {
+                  DexClassAndMethod method = resolutionResult.getResolutionPair();
+                  DexMethod companionMethod;
+                  if (method.isProgramMethod()) {
+                    ProgramMethod companionMethodDefinition =
+                        helper.ensurePrivateAsMethodOfProgramCompanionClassStub(
+                            method.asProgramMethod());
+                    companionMethod = companionMethodDefinition.getReference();
+                    eventConsumer.acceptCompanionMethod(
+                        method.asProgramMethod(), companionMethodDefinition);
+                  } else {
+                    companionMethod = helper.privateAsMethodOfCompanionClass(method);
+                  }
+                  return getInvokeStaticInstructions(companionMethod);
+                })
+            .build();
       } else {
-        DexClassAndMethod method = resolutionResult.getResolutionPair();
-        // TODO(b/183998768): Why do this amend routine. We have done resolution, so would that
-        //  not be the correct target!? I think this is just legacy from before resolution was
-        //  implemented in full.
-        DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
-        assert method.getReference() == amendedMethod;
-        DexClassAndMethod companionMethod =
-            helper.ensureDefaultAsMethodOfCompanionClassStub(method);
-        acceptCompanionMethod(method, companionMethod, eventConsumer);
-        return rewriteInvoke.apply(
-            InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
-                amendedMethod, appView.dexItemFactory()));
+        return DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context12,
+                    methodProcessingContext,
+                    dexItemFactory) -> {
+                  DexClassAndMethod method = resolutionResult.getResolutionPair();
+                  // TODO(b/183998768): Why do this amend routine. We have done resolution, so would
+                  // that
+                  //  not be the correct target!? I think this is just legacy from before resolution
+                  // was
+                  //  implemented in full.
+                  DexMethod amendedMethod =
+                      amendDefaultMethod(context12.getHolder(), invokedMethod);
+                  assert method.getReference() == amendedMethod;
+                  DexClassAndMethod companionMethod =
+                      helper.ensureDefaultAsMethodOfCompanionClassStub(method);
+                  acceptCompanionMethod(method, companionMethod, eventConsumer);
+                  return getInvokeStaticInstructions(
+                      InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
+                          amendedMethod, appView.dexItemFactory()));
+                })
+            .build();
       }
     }
 
@@ -828,15 +803,24 @@
         if (target != null && target.getDefinition().isDefaultMethod()) {
           DexClass holder = target.getHolder();
           if (holder.isLibraryClass() && holder.isInterface()) {
-            assert invokeNeedsRewriting(invokedMethod, SUPER);
-            DexClassAndMethod companionTarget =
-                helper.ensureDefaultAsMethodOfCompanionClassStub(target);
-            acceptCompanionMethod(target, companionTarget, eventConsumer);
-            return rewriteInvoke.apply(companionTarget.getReference());
+            return DesugarDescription.builder()
+                .setDesugarRewrite(
+                    (freshLocalProvider,
+                        localStackAllocator,
+                        eventConsumer,
+                        context13,
+                        methodProcessingContext,
+                        dexItemFactory) -> {
+                      DexClassAndMethod companionTarget =
+                          helper.ensureDefaultAsMethodOfCompanionClassStub(target);
+                      acceptCompanionMethod(target, companionTarget, eventConsumer);
+                      return getInvokeStaticInstructions(companionTarget.getReference());
+                    })
+                .build();
           }
         }
       }
-      return null;
+      return DesugarDescription.nothing();
     }
     // That invoke super may not resolve since the super method may not be present
     // since it's in the emulated interface. We need to force resolution. If it resolves
@@ -845,26 +829,43 @@
     DexClassAndMethod superTarget =
         appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
     if (superTarget != null && superTarget.isLibraryMethod()) {
-      assert invokeNeedsRewriting(invokedMethod, SUPER);
       // Rewriting is required because the super invoke resolves into a missing
       // method (method is on desugared library). Find out if it needs to be
       // retargeted or if it just calls a companion class method and rewrite.
       DexMethod retargetMethod =
           options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
       if (retargetMethod != null) {
-        return rewriteInvoke.apply(retargetMethod);
+        return DesugarDescription.builder()
+            .setDesugarRewrite(
+                (freshLocalProvider,
+                    localStackAllocator,
+                    eventConsumer,
+                    context14,
+                    methodProcessingContext,
+                    dexItemFactory) -> getInvokeStaticInstructions(retargetMethod))
+            .build();
       }
       DexClassAndMethod emulatedMethod =
           superTarget.getReference().lookupMemberOnClass(appView.definitionFor(emulatedItf));
       if (emulatedMethod == null) {
         assert false;
-        return null;
+        return DesugarDescription.nothing();
       }
-      DexClassAndMethod companionMethod =
-          helper.ensureDefaultAsMethodOfCompanionClassStub(emulatedMethod);
-      return rewriteInvoke.apply(companionMethod.getReference());
+      return DesugarDescription.builder()
+          .setDesugarRewrite(
+              (freshLocalProvider,
+                  localStackAllocator,
+                  eventConsumer,
+                  context15,
+                  methodProcessingContext,
+                  dexItemFactory) -> {
+                DexClassAndMethod companionMethod =
+                    helper.ensureDefaultAsMethodOfCompanionClassStub(emulatedMethod);
+                return getInvokeStaticInstructions(companionMethod.getReference());
+              })
+          .build();
     }
-    return null;
+    return DesugarDescription.nothing();
   }
 
   private DexClassAndMethod defaultMethodForEmulatedDispatchOrNull(
@@ -893,20 +894,6 @@
     return null;
   }
 
-  private Collection<CfInstruction> rewriteInvokeInterfaceOrInvokeVirtual(
-      DexMethod invokedMethod,
-      boolean interfaceBit,
-      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
-      CfInstructionDesugaringEventConsumer eventConsumer) {
-    DexClassAndMethod defaultMethod =
-        defaultMethodForEmulatedDispatchOrNull(invokedMethod, interfaceBit);
-    if (defaultMethod != null) {
-      return rewriteInvoke.apply(
-          helper.ensureEmulatedInterfaceMethod(defaultMethod, eventConsumer).getReference());
-    }
-    return null;
-  }
-
   private boolean shouldRewriteToInvokeToThrow(
       SingleResolutionResult resolutionResult, boolean isInvokeStatic) {
     return resolutionResult == null