Avoid excessive parameter passing in argument propagator

Change-Id: I6154087bd20af481a44ed4b348646f98a8edc1c4
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 4f374e6..8b19ac3 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -211,644 +211,610 @@
       IRCode code,
       AbstractValueSupplier abstractValueSupplier,
       Timing timing) {
-    timing.begin("Argument propagation scanner");
-    for (Instruction instruction : code.instructions()) {
-      if (instruction.isFieldPut()) {
-        scan(instruction.asFieldPut(), abstractValueSupplier, method, timing);
-      } else if (instruction.isInvokeMethod()) {
-        scan(instruction.asInvokeMethod(), abstractValueSupplier, method, timing);
-      } else if (instruction.isInvokeCustom()) {
-        scan(instruction.asInvokeCustom());
-      }
-    }
-    timing.end();
+    new CodeScanner(abstractValueSupplier, code, method).scan(timing);
   }
 
-  private void scan(
-      FieldPut fieldPut,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      Timing timing) {
-    ProgramField field = fieldPut.resolveField(appView, context).getProgramField();
-    if (field == null) {
-      // Nothing to propagate.
-      return;
-    }
-    addTemporaryFieldState(fieldPut, field, abstractValueSupplier, context, timing);
-  }
+  protected class CodeScanner {
 
-  private void addTemporaryFieldState(
-      FieldPut fieldPut,
-      ProgramField field,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      Timing timing) {
-    timing.begin("Add field state");
-    fieldStates.addTemporaryFieldState(
-        field,
-        () -> computeFieldState(fieldPut, field, abstractValueSupplier, context, timing),
-        timing,
-        (existingFieldState, fieldStateToAdd) -> {
-          DexType inStaticType = null;
-          NonEmptyValueState newFieldState =
-              existingFieldState.mutableJoin(
-                  appView,
-                  fieldStateToAdd,
-                  inStaticType,
-                  field.getType(),
-                  StateCloner.getCloner(),
-                  Action.empty());
-          return narrowFieldState(field, newFieldState);
-        });
-    timing.end();
-  }
+    protected final AbstractValueSupplier abstractValueSupplier;
+    protected final IRCode code;
+    protected final ProgramMethod context;
 
-  private NonEmptyValueState computeFieldState(
-      FieldPut fieldPut,
-      ProgramField resolvedField,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      Timing timing) {
-    timing.begin("Compute field state for field-put");
-    NonEmptyValueState result =
-        computeFieldState(fieldPut, resolvedField, abstractValueSupplier, context);
-    timing.end();
-    return result;
-  }
-
-  private NonEmptyValueState computeFieldState(
-      FieldPut fieldPut,
-      ProgramField field,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context) {
-    TypeElement fieldType = field.getType().toTypeElement(appView);
-    if (!fieldPut.value().getType().lessThanOrEqual(fieldType, appView)) {
-      return ValueState.unknown();
+    protected CodeScanner(
+        AbstractValueSupplier abstractValueSupplier, IRCode code, ProgramMethod method) {
+      this.abstractValueSupplier = abstractValueSupplier;
+      this.code = code;
+      this.context = method;
     }
 
-    NonEmptyValueState inFlowState = computeInFlowState(field.getType(), fieldPut.value(), context);
-    if (inFlowState != null) {
-      return inFlowState;
-    }
-
-    if (field.getType().isArrayType()) {
-      Nullability nullability = fieldPut.value().getType().nullability();
-      return ConcreteArrayTypeValueState.create(nullability);
-    }
-
-    AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
-    if (abstractValue.isUnknown()) {
-      abstractValue =
-          getFallbackAbstractValueForField(
-              field,
-              () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
-    }
-    if (field.getType().isClassType()) {
-      DynamicType dynamicType =
-          WideningUtils.widenDynamicNonReceiverType(
-              appView, fieldPut.value().getDynamicType(appView), field.getType());
-      return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
-    } else {
-      assert field.getType().isPrimitiveType();
-      return ConcretePrimitiveTypeValueState.create(abstractValue);
-    }
-  }
-
-  // If the value is an argument of the enclosing method or defined by a field-get, then clearly we
-  // have no information about its abstract value (yet). Instead of treating this as having an
-  // unknown runtime value, we instead record a flow constraint.
-  private InFlow computeInFlow(DexType staticType, Value value, ProgramMethod context) {
-    Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
-    if (valueRoot.isArgument()) {
-      MethodParameter inParameter =
-          methodParameterFactory.create(context, valueRoot.getDefinition().asArgument().getIndex());
-      return castBaseInFlow(widenBaseInFlow(staticType, inParameter, context), value);
-    } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
-      FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
-      ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
-      if (field == null) {
-        return null;
-      }
-      if (fieldGet.isInstanceGet()) {
-        Value receiverValue = fieldGet.asInstanceGet().object();
-        BaseInFlow receiverInFlow =
-            asBaseInFlowOrNull(computeInFlow(staticType, receiverValue, context));
-        if (receiverInFlow != null
-            && receiverInFlow.equals(widenBaseInFlow(staticType, receiverInFlow, context))) {
-          return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+    public void scan(Timing timing) {
+      timing.begin("Argument propagation scanner");
+      for (Instruction instruction : code.instructions()) {
+        if (instruction.isFieldPut()) {
+          scanFieldPut(instruction.asFieldPut(), timing);
+        } else if (instruction.isInvokeMethod()) {
+          scanInvoke(instruction.asInvokeMethod(), timing);
+        } else if (instruction.isInvokeCustom()) {
+          scanInvokeCustom(instruction.asInvokeCustom());
         }
       }
-      return castBaseInFlow(
-          widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
+      timing.end();
     }
-    return null;
-  }
 
-  private InFlow castBaseInFlow(InFlow inFlow, Value value) {
-    if (inFlow.isUnknownAbstractFunction()) {
-      return inFlow;
-    }
-    assert inFlow.isBaseInFlow();
-    Value valueRoot = value.getAliasedValue();
-    if (!valueRoot.isDefinedByInstructionSatisfying(Instruction::isCheckCast)) {
-      return inFlow;
-    }
-    CheckCast checkCast = valueRoot.getDefinition().asCheckCast();
-    return new CastAbstractFunction(inFlow.asBaseInFlow(), checkCast.getType());
-  }
-
-  private InFlow widenBaseInFlow(DexType staticType, BaseInFlow inFlow, ProgramMethod context) {
-    if (inFlow.isFieldValue()) {
-      if (isFieldValueAlreadyUnknown(staticType, inFlow.asFieldValue().getField())) {
-        return AbstractFunction.unknown();
+    private void scanFieldPut(FieldPut fieldPut, Timing timing) {
+      ProgramField field = fieldPut.resolveField(appView, context).getProgramField();
+      if (field == null) {
+        // Nothing to propagate.
+        return;
       }
-    } else {
-      assert inFlow.isMethodParameter();
-      if (isMethodParameterAlreadyUnknown(staticType, inFlow.asMethodParameter(), context)) {
-        return AbstractFunction.unknown();
-      }
+      addTemporaryFieldState(fieldPut, field, timing);
     }
-    return inFlow;
-  }
 
-  private NonEmptyValueState computeInFlowState(
-      DexType staticType, Value value, ProgramMethod context) {
-    InFlow inFlow = computeInFlow(staticType, value, context);
-    if (inFlow == null) {
-      return null;
+    private void addTemporaryFieldState(FieldPut fieldPut, ProgramField field, Timing timing) {
+      timing.begin("Add field state");
+      fieldStates.addTemporaryFieldState(
+          field,
+          () -> computeFieldState(fieldPut, field, timing),
+          timing,
+          (existingFieldState, fieldStateToAdd) -> {
+            DexType inStaticType = null;
+            NonEmptyValueState newFieldState =
+                existingFieldState.mutableJoin(
+                    appView,
+                    fieldStateToAdd,
+                    inStaticType,
+                    field.getType(),
+                    StateCloner.getCloner(),
+                    Action.empty());
+            return narrowFieldState(field, newFieldState);
+          });
+      timing.end();
     }
-    if (inFlow.isUnknownAbstractFunction()) {
-      return ValueState.unknown();
-    }
-    assert inFlow.isBaseInFlow()
-        || inFlow.isCastAbstractFunction()
-        || inFlow.isInstanceFieldReadAbstractFunction();
-    return ConcreteValueState.create(staticType, inFlow);
-  }
 
-  // Strengthens the abstract value of static final fields to a (self-)SingleFieldValue when the
-  // abstract value is unknown. The soundness of this is based on the fact that static final fields
-  // will never have their value changed after the <clinit> finishes, so value in a static final
-  // field can always be rematerialized by reading the field.
-  private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
-    AbstractValue fallbackAbstractValue =
-        getFallbackAbstractValueForField(field, ObjectState::empty);
-    if (!fallbackAbstractValue.isUnknown()) {
-      AbstractValue abstractValue = fieldState.getAbstractValue(appView);
-      if (!abstractValue.isUnknown()) {
-        return fieldState;
+    private NonEmptyValueState computeFieldState(
+        FieldPut fieldPut, ProgramField resolvedField, Timing timing) {
+      timing.begin("Compute field state for field-put");
+      NonEmptyValueState result = computeFieldState(fieldPut, resolvedField);
+      timing.end();
+      return result;
+    }
+
+    private NonEmptyValueState computeFieldState(FieldPut fieldPut, ProgramField field) {
+      TypeElement fieldType = field.getType().toTypeElement(appView);
+      if (!fieldPut.value().getType().lessThanOrEqual(fieldType, appView)) {
+        return ValueState.unknown();
       }
+
+      NonEmptyValueState inFlowState =
+          computeInFlowState(field.getType(), fieldPut.value(), context);
+      if (inFlowState != null) {
+        return inFlowState;
+      }
+
       if (field.getType().isArrayType()) {
-        // We do not track an abstract value for array types.
-        return fieldState;
+        Nullability nullability = fieldPut.value().getType().nullability();
+        return ConcreteArrayTypeValueState.create(nullability);
+      }
+
+      AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(fieldPut.value());
+      if (abstractValue.isUnknown()) {
+        abstractValue =
+            getFallbackAbstractValueForField(
+                field,
+                () -> ObjectStateAnalysis.computeObjectState(fieldPut.value(), appView, context));
       }
       if (field.getType().isClassType()) {
         DynamicType dynamicType =
-            fieldState.isReferenceState()
-                ? fieldState.asReferenceState().getDynamicType()
-                : DynamicType.unknown();
-        return new ConcreteClassTypeValueState(fallbackAbstractValue, dynamicType);
+            WideningUtils.widenDynamicNonReceiverType(
+                appView, fieldPut.value().getDynamicType(appView), field.getType());
+        return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
       } else {
         assert field.getType().isPrimitiveType();
-        return new ConcretePrimitiveTypeValueState(fallbackAbstractValue);
+        return ConcretePrimitiveTypeValueState.create(abstractValue);
       }
     }
-    return fieldState;
-  }
 
-  private AbstractValue getFallbackAbstractValueForField(
-      ProgramField field, Supplier<ObjectState> objectStateSupplier) {
-    if (field.isFinalOrEffectivelyFinal(appView) && field.getAccessFlags().isStatic()) {
-      return appView
-          .abstractValueFactory()
-          .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
-    }
-    return AbstractValue.unknown();
-  }
-
-  private void scan(
-      InvokeMethod invoke,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      Timing timing) {
-    DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod.getHolderType().isArrayType()) {
-      // Nothing to propagate; the targeted method is not a program method.
-      return;
+    // If the value is an argument of the enclosing method or defined by a field-get, then clearly
+    // we have no information about its abstract value (yet). Instead of treating this as having an
+    // unknown runtime value, we instead record a flow constraint.
+    private InFlow computeInFlow(DexType staticType, Value value, ProgramMethod context) {
+      Value valueRoot = value.getAliasedValue(aliasedValueConfiguration);
+      if (valueRoot.isArgument()) {
+        MethodParameter inParameter =
+            methodParameterFactory.create(
+                context, valueRoot.getDefinition().asArgument().getIndex());
+        return castBaseInFlow(widenBaseInFlow(staticType, inParameter, context), value);
+      } else if (valueRoot.isDefinedByInstructionSatisfying(Instruction::isFieldGet)) {
+        FieldGet fieldGet = valueRoot.getDefinition().asFieldGet();
+        ProgramField field = fieldGet.resolveField(appView, context).getProgramField();
+        if (field == null) {
+          return null;
+        }
+        if (fieldGet.isInstanceGet()) {
+          Value receiverValue = fieldGet.asInstanceGet().object();
+          BaseInFlow receiverInFlow =
+              asBaseInFlowOrNull(computeInFlow(staticType, receiverValue, context));
+          if (receiverInFlow != null
+              && receiverInFlow.equals(widenBaseInFlow(staticType, receiverInFlow, context))) {
+            return new InstanceFieldReadAbstractFunction(receiverInFlow, field.getReference());
+          }
+        }
+        return castBaseInFlow(
+            widenBaseInFlow(staticType, fieldValueFactory.create(field), context), value);
+      }
+      return null;
     }
 
-    if (appView.options().testing.checkReceiverAlwaysNullInCallSiteOptimization
-        && invoke.isInvokeMethodWithReceiver()
-        && invoke.asInvokeMethodWithReceiver().getReceiver().isAlwaysNull(appView)) {
-      // Nothing to propagate; the invoke instruction always fails.
-      return;
+    private InFlow castBaseInFlow(InFlow inFlow, Value value) {
+      if (inFlow.isUnknownAbstractFunction()) {
+        return inFlow;
+      }
+      assert inFlow.isBaseInFlow();
+      Value valueRoot = value.getAliasedValue();
+      if (!valueRoot.isDefinedByInstructionSatisfying(Instruction::isCheckCast)) {
+        return inFlow;
+      }
+      CheckCast checkCast = valueRoot.getDefinition().asCheckCast();
+      return new CastAbstractFunction(inFlow.asBaseInFlow(), checkCast.getType());
     }
 
-    SingleResolutionResult<?> resolutionResult =
-        invoke.resolveMethod(appView, context).asSingleResolution();
-    if (resolutionResult == null) {
-      // Nothing to propagate; the invoke instruction fails.
-      return;
+    private InFlow widenBaseInFlow(DexType staticType, BaseInFlow inFlow, ProgramMethod context) {
+      if (inFlow.isFieldValue()) {
+        if (isFieldValueAlreadyUnknown(staticType, inFlow.asFieldValue().getField())) {
+          return AbstractFunction.unknown();
+        }
+      } else {
+        assert inFlow.isMethodParameter();
+        if (isMethodParameterAlreadyUnknown(staticType, inFlow.asMethodParameter(), context)) {
+          return AbstractFunction.unknown();
+        }
+      }
+      return inFlow;
     }
 
-    if (!resolutionResult.getResolvedHolder().isProgramClass()) {
-      // Nothing to propagate; this could dispatch to a program method, but we cannot optimize
-      // methods that override non-program methods.
-      return;
+    private NonEmptyValueState computeInFlowState(
+        DexType staticType, Value value, ProgramMethod context) {
+      InFlow inFlow = computeInFlow(staticType, value, context);
+      if (inFlow == null) {
+        return null;
+      }
+      if (inFlow.isUnknownAbstractFunction()) {
+        return ValueState.unknown();
+      }
+      assert inFlow.isBaseInFlow()
+          || inFlow.isCastAbstractFunction()
+          || inFlow.isInstanceFieldReadAbstractFunction();
+      return ConcreteValueState.create(staticType, inFlow);
     }
 
-    ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
-    if (resolvedMethod.getDefinition().isLibraryMethodOverride().isPossiblyTrue()) {
-      assert resolvedMethod.getDefinition().isLibraryMethodOverride().isTrue();
-      // Nothing to propagate; we don't know anything about methods that can be called from outside
-      // the program.
-      return;
+    // Strengthens the abstract value of static final fields to a (self-)SingleFieldValue when the
+    // abstract value is unknown. The soundness of this is based on the fact that static final
+    // fields will never have their value changed after the <clinit> finishes, so value in a static
+    // final field can always be rematerialized by reading the field.
+    private NonEmptyValueState narrowFieldState(ProgramField field, NonEmptyValueState fieldState) {
+      AbstractValue fallbackAbstractValue =
+          getFallbackAbstractValueForField(field, ObjectState::empty);
+      if (!fallbackAbstractValue.isUnknown()) {
+        AbstractValue abstractValue = fieldState.getAbstractValue(appView);
+        if (!abstractValue.isUnknown()) {
+          return fieldState;
+        }
+        if (field.getType().isArrayType()) {
+          // We do not track an abstract value for array types.
+          return fieldState;
+        }
+        if (field.getType().isClassType()) {
+          DynamicType dynamicType =
+              fieldState.isReferenceState()
+                  ? fieldState.asReferenceState().getDynamicType()
+                  : DynamicType.unknown();
+          return new ConcreteClassTypeValueState(fallbackAbstractValue, dynamicType);
+        } else {
+          assert field.getType().isPrimitiveType();
+          return new ConcretePrimitiveTypeValueState(fallbackAbstractValue);
+        }
+      }
+      return fieldState;
     }
 
-    if (invoke.arguments().size() != resolvedMethod.getDefinition().getNumberOfArguments()
-        || invoke.isInvokeStatic() != resolvedMethod.getAccessFlags().isStatic()) {
-      // Nothing to propagate; the invoke instruction fails.
-      return;
+    private AbstractValue getFallbackAbstractValueForField(
+        ProgramField field, Supplier<ObjectState> objectStateSupplier) {
+      if (field.isFinalOrEffectivelyFinal(appView) && field.getAccessFlags().isStatic()) {
+        return appView
+            .abstractValueFactory()
+            .createSingleFieldValue(field.getReference(), objectStateSupplier.get());
+      }
+      return AbstractValue.unknown();
     }
 
-    if (invoke.isInvokeInterface()) {
-      if (!resolutionResult.getInitialResolutionHolder().isInterface()) {
+    private void scanInvoke(InvokeMethod invoke, Timing timing) {
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (invokedMethod.getHolderType().isArrayType()) {
+        // Nothing to propagate; the targeted method is not a program method.
+        return;
+      }
+
+      if (appView.options().testing.checkReceiverAlwaysNullInCallSiteOptimization
+          && invoke.isInvokeMethodWithReceiver()
+          && invoke.asInvokeMethodWithReceiver().getReceiver().isAlwaysNull(appView)) {
+        // Nothing to propagate; the invoke instruction always fails.
+        return;
+      }
+
+      SingleResolutionResult<?> resolutionResult =
+          invoke.resolveMethod(appView, context).asSingleResolution();
+      if (resolutionResult == null) {
         // Nothing to propagate; the invoke instruction fails.
         return;
       }
-    }
 
-    if (invoke.isInvokeSuper()) {
-      // Use the super target instead of the resolved method to ensure that we propagate the
-      // argument information to the targeted method.
-      DexClassAndMethod target =
-          resolutionResult.lookupInvokeSuperTarget(context.getHolder(), appView);
-      if (target == null) {
+      if (!resolutionResult.getResolvedHolder().isProgramClass()) {
+        // Nothing to propagate; this could dispatch to a program method, but we cannot optimize
+        // methods that override non-program methods.
+        return;
+      }
+
+      ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
+      if (resolvedMethod.getDefinition().isLibraryMethodOverride().isPossiblyTrue()) {
+        assert resolvedMethod.getDefinition().isLibraryMethodOverride().isTrue();
+        // Nothing to propagate; we don't know anything about methods that can be called from
+        // outside the program.
+        return;
+      }
+
+      if (invoke.arguments().size() != resolvedMethod.getDefinition().getNumberOfArguments()
+          || invoke.isInvokeStatic() != resolvedMethod.getAccessFlags().isStatic()) {
         // Nothing to propagate; the invoke instruction fails.
         return;
       }
-      if (!target.isProgramMethod()) {
-        throw new Unreachable(
-            "Expected super target of a non-library override to be a program method ("
-                + "resolved program method: "
-                + resolvedMethod
-                + ", "
-                + "super non-program method: "
-                + target
-                + ")");
+
+      if (invoke.isInvokeInterface()) {
+        if (!resolutionResult.getInitialResolutionHolder().isInterface()) {
+          // Nothing to propagate; the invoke instruction fails.
+          return;
+        }
       }
-      resolvedMethod = target.asProgramMethod();
+
+      if (invoke.isInvokeSuper()) {
+        // Use the super target instead of the resolved method to ensure that we propagate the
+        // argument information to the targeted method.
+        DexClassAndMethod target =
+            resolutionResult.lookupInvokeSuperTarget(context.getHolder(), appView);
+        if (target == null) {
+          // Nothing to propagate; the invoke instruction fails.
+          return;
+        }
+        if (!target.isProgramMethod()) {
+          throw new Unreachable(
+              "Expected super target of a non-library override to be a program method ("
+                  + "resolved program method: "
+                  + resolvedMethod
+                  + ", "
+                  + "super non-program method: "
+                  + target
+                  + ")");
+        }
+        resolvedMethod = target.asProgramMethod();
+      }
+
+      // Find the method where to store the information about the arguments from this invoke.
+      // If the invoke may dispatch to more than one method, we intentionally do not compute all
+      // possible dispatch targets and propagate the information to these methods (this is
+      // expensive). Instead we record the information in one place and then later propagate the
+      // information to all dispatch targets.
+      addTemporaryMethodState(invoke, resolvedMethod, timing);
     }
 
-    // Find the method where to store the information about the arguments from this invoke.
-    // If the invoke may dispatch to more than one method, we intentionally do not compute all
-    // possible dispatch targets and propagate the information to these methods (this is expensive).
-    // Instead we record the information in one place and then later propagate the information to
-    // all dispatch targets.
-    addTemporaryMethodState(invoke, resolvedMethod, abstractValueSupplier, context, timing);
-  }
+    protected void addTemporaryMethodState(
+        InvokeMethod invoke, ProgramMethod resolvedMethod, Timing timing) {
+      timing.begin("Add method state");
+      methodStates.addTemporaryMethodState(
+          appView,
+          getRepresentative(invoke, resolvedMethod),
+          existingMethodState ->
+              computeMethodState(invoke, resolvedMethod, existingMethodState, timing),
+          timing);
+      timing.end();
+    }
 
-  protected void addTemporaryMethodState(
-      InvokeMethod invoke,
-      ProgramMethod resolvedMethod,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      Timing timing) {
-    timing.begin("Add method state");
-    methodStates.addTemporaryMethodState(
-        appView,
-        getRepresentative(invoke, resolvedMethod),
-        existingMethodState ->
-            computeMethodState(
+    private MethodState computeMethodState(
+        InvokeMethod invoke,
+        ProgramMethod resolvedMethod,
+        MethodState existingMethodState,
+        Timing timing) {
+      assert !existingMethodState.isUnknown();
+
+      // If this invoke may target at most one method, then we compute a state that maps each
+      // parameter to the abstract value and dynamic type provided by this call site. Otherwise, we
+      // compute a polymorphic method state, which includes information about the receiver's dynamic
+      // type bounds.
+      timing.begin("Compute method state for invoke");
+      MethodState result;
+      if (shouldUsePolymorphicMethodState(invoke, resolvedMethod)) {
+        assert existingMethodState.isBottom() || existingMethodState.isPolymorphic();
+        result =
+            computePolymorphicMethodState(
+                invoke.asInvokeMethodWithReceiver(),
+                resolvedMethod,
+                existingMethodState.asPolymorphicOrBottom());
+      } else {
+        assert existingMethodState.isBottom() || existingMethodState.isMonomorphic();
+        result =
+            computeMonomorphicMethodState(
                 invoke,
                 resolvedMethod,
-                abstractValueSupplier,
-                context,
-                existingMethodState,
-                timing),
-        timing);
-    timing.end();
-  }
+                invoke.lookupSingleProgramTarget(appView, context),
+                existingMethodState.asMonomorphicOrBottom());
+      }
+      timing.end();
+      return result;
+    }
 
-  private MethodState computeMethodState(
-      InvokeMethod invoke,
-      ProgramMethod resolvedMethod,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      MethodState existingMethodState,
-      Timing timing) {
-    assert !existingMethodState.isUnknown();
+    // TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily
+    //  experimenting with the performance/size trade-off between precise/imprecise handling of
+    //  dynamic dispatch.
+    private MethodState computePolymorphicMethodState(
+        InvokeMethodWithReceiver invoke,
+        ProgramMethod resolvedMethod,
+        ConcretePolymorphicMethodStateOrBottom existingMethodState) {
+      DynamicTypeWithUpperBound dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
+      // TODO(b/331587404): Investigate if we can replace the receiver by null before entering this
+      //  pass, so that this special case is not needed.
+      if (dynamicReceiverType.isNullType()) {
+        assert appView.testing().allowNullDynamicTypeInCodeScanner : "b/250634405";
+        // This can happen if we were unable to determine that the receiver is a phi value where
+        // null information has not been propagated down. Ideally this case would never happen as it
+        // should be possible to replace the receiver by the null constant in this case. Since the
+        // receiver is known to be null, no argument information should be propagated to the
+        // callees, so we return bottom here.
+        return MethodState.bottom();
+      }
 
-    // If this invoke may target at most one method, then we compute a state that maps each
-    // parameter to the abstract value and dynamic type provided by this call site. Otherwise, we
-    // compute a polymorphic method state, which includes information about the receiver's dynamic
-    // type bounds.
-    timing.begin("Compute method state for invoke");
-    MethodState result;
-    if (shouldUsePolymorphicMethodState(invoke, resolvedMethod)) {
-      assert existingMethodState.isBottom() || existingMethodState.isPolymorphic();
-      result =
-          computePolymorphicMethodState(
-              invoke.asInvokeMethodWithReceiver(),
-              resolvedMethod,
-              abstractValueSupplier,
-              context,
-              existingMethodState.asPolymorphicOrBottom());
-    } else {
-      assert existingMethodState.isBottom() || existingMethodState.isMonomorphic();
-      result =
+      ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
+      DynamicTypeWithUpperBound bounds =
+          computeBoundsForPolymorphicMethodState(resolvedMethod, singleTarget, dynamicReceiverType);
+      MethodState existingMethodStateForBounds =
+          existingMethodState.isPolymorphic()
+              ? existingMethodState.asPolymorphic().getMethodStateForBounds(bounds)
+              : MethodState.bottom();
+
+      if (existingMethodStateForBounds.isPolymorphic()) {
+        assert false;
+        return MethodState.unknown();
+      }
+
+      // If we already don't know anything about the parameters for the given type bounds, then
+      // don't compute a method state.
+      if (existingMethodStateForBounds.isUnknown()) {
+        return MethodState.bottom();
+      }
+
+      ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds =
           computeMonomorphicMethodState(
               invoke,
               resolvedMethod,
-              invoke.lookupSingleProgramTarget(appView, context),
-              abstractValueSupplier,
-              context,
-              existingMethodState.asMonomorphicOrBottom());
-    }
-    timing.end();
-    return result;
-  }
-
-  // TODO(b/190154391): Add a strategy that widens the dynamic receiver type to allow easily
-  //  experimenting with the performance/size trade-off between precise/imprecise handling of
-  //  dynamic dispatch.
-  private MethodState computePolymorphicMethodState(
-      InvokeMethodWithReceiver invoke,
-      ProgramMethod resolvedMethod,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      ConcretePolymorphicMethodStateOrBottom existingMethodState) {
-    DynamicTypeWithUpperBound dynamicReceiverType = invoke.getReceiver().getDynamicType(appView);
-    // TODO(b/331587404): Investigate if we can replace the receiver by null before entering this
-    //  pass, so that this special case is not needed.
-    if (dynamicReceiverType.isNullType()) {
-      assert appView.testing().allowNullDynamicTypeInCodeScanner : "b/250634405";
-      // This can happen if we were unable to determine that the receiver is a phi value where null
-      // information has not been propagated down. Ideally this case would never happen as it should
-      // be possible to replace the receiver by the null constant in this case.
-      //
-      // Since the receiver is known to be null, no argument information should be propagated to the
-      // callees, so we return bottom here.
-      return MethodState.bottom();
+              singleTarget,
+              existingMethodStateForBounds.asMonomorphicOrBottom(),
+              dynamicReceiverType);
+      return ConcretePolymorphicMethodState.create(bounds, methodStateForBounds);
     }
 
-    ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, context);
-    DynamicTypeWithUpperBound bounds =
-        computeBoundsForPolymorphicMethodState(resolvedMethod, singleTarget, dynamicReceiverType);
-    MethodState existingMethodStateForBounds =
-        existingMethodState.isPolymorphic()
-            ? existingMethodState.asPolymorphic().getMethodStateForBounds(bounds)
-            : MethodState.bottom();
+    private DynamicTypeWithUpperBound computeBoundsForPolymorphicMethodState(
+        ProgramMethod resolvedMethod,
+        ProgramMethod singleTarget,
+        DynamicTypeWithUpperBound dynamicReceiverType) {
+      DynamicTypeWithUpperBound bounds =
+          singleTarget != null
+              ? DynamicType.createExact(
+                  singleTarget.getHolderType().toTypeElement(appView).asClassType())
+              : dynamicReceiverType.withNullability(Nullability.maybeNull());
 
-    if (existingMethodStateForBounds.isPolymorphic()) {
-      assert false;
-      return MethodState.unknown();
-    }
+      // We intentionally drop the nullability for the type bounds. This increases the number of
+      // collisions in the polymorphic method states, which does not change the precision (since the
+      // nullability does not have any impact on the possible dispatch targets) and is good for
+      // state pruning.
+      assert bounds.getDynamicUpperBoundType().nullability().isMaybeNull();
 
-    // If we already don't know anything about the parameters for the given type bounds, then don't
-    // compute a method state.
-    if (existingMethodStateForBounds.isUnknown()) {
-      return MethodState.bottom();
-    }
+      // If the bounds are trivial (i.e., the upper bound is equal to the holder of the virtual root
+      // method), then widen the type bounds to 'unknown'.
+      DexMethod virtualRootMethod = getVirtualRootMethod(resolvedMethod);
+      if (virtualRootMethod == null) {
+        assert false : "Unexpected virtual method without root: " + resolvedMethod;
+        return bounds;
+      }
 
-    ConcreteMonomorphicMethodStateOrUnknown methodStateForBounds =
-        computeMonomorphicMethodState(
-            invoke,
-            resolvedMethod,
-            singleTarget,
-            abstractValueSupplier,
-            context,
-            existingMethodStateForBounds.asMonomorphicOrBottom(),
-            dynamicReceiverType);
-    return ConcretePolymorphicMethodState.create(bounds, methodStateForBounds);
-  }
-
-  private DynamicTypeWithUpperBound computeBoundsForPolymorphicMethodState(
-      ProgramMethod resolvedMethod,
-      ProgramMethod singleTarget,
-      DynamicTypeWithUpperBound dynamicReceiverType) {
-    DynamicTypeWithUpperBound bounds =
-        singleTarget != null
-            ? DynamicType.createExact(
-                singleTarget.getHolderType().toTypeElement(appView).asClassType())
-            : dynamicReceiverType.withNullability(Nullability.maybeNull());
-
-    // We intentionally drop the nullability for the type bounds. This increases the number of
-    // collisions in the polymorphic method states, which does not change the precision (since the
-    // nullability does not have any impact on the possible dispatch targets) and is good for state
-    // pruning.
-    assert bounds.getDynamicUpperBoundType().nullability().isMaybeNull();
-
-    // If the bounds are trivial (i.e., the upper bound is equal to the holder of the virtual root
-    // method), then widen the type bounds to 'unknown'.
-    DexMethod virtualRootMethod = getVirtualRootMethod(resolvedMethod);
-    if (virtualRootMethod == null) {
-      assert false : "Unexpected virtual method without root: " + resolvedMethod;
+      DynamicType trivialBounds =
+          DynamicType.create(
+              appView, virtualRootMethod.getHolderType().toTypeElement(appView).asClassType());
+      if (bounds.equals(trivialBounds)) {
+        return DynamicType.unknown();
+      }
       return bounds;
     }
 
-    DynamicType trivialBounds =
-        DynamicType.create(
-            appView, virtualRootMethod.getHolderType().toTypeElement(appView).asClassType());
-    if (bounds.equals(trivialBounds)) {
-      return DynamicType.unknown();
-    }
-    return bounds;
-  }
-
-  private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
-      InvokeMethod invoke,
-      ProgramMethod resolvedMethod,
-      ProgramMethod singleTarget,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
-    return computeMonomorphicMethodState(
-        invoke,
-        resolvedMethod,
-        singleTarget,
-        abstractValueSupplier,
-        context,
-        existingMethodState,
-        invoke.isInvokeMethodWithReceiver()
-            ? invoke.getFirstArgument().getDynamicType(appView)
-            : null);
-  }
-
-  @SuppressWarnings("UnusedVariable")
-  private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
-      InvokeMethod invoke,
-      ProgramMethod resolvedMethod,
-      ProgramMethod singleTarget,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      ConcreteMonomorphicMethodStateOrBottom existingMethodState,
-      DynamicType dynamicReceiverType) {
-    List<ValueState> parameterStates = new ArrayList<>(invoke.arguments().size());
-
-    MethodReprocessingCriteria methodReprocessingCriteria =
-        singleTarget != null
-            ? reprocessingCriteriaCollection.getReprocessingCriteria(singleTarget)
-            : MethodReprocessingCriteria.alwaysReprocess();
-
-    int argumentIndex = 0;
-    if (invoke.isInvokeMethodWithReceiver()) {
-      assert dynamicReceiverType != null;
-      parameterStates.add(
-          computeParameterStateForReceiver(
-              resolvedMethod,
-              dynamicReceiverType,
-              existingMethodState,
-              methodReprocessingCriteria.getParameterReprocessingCriteria(0)));
-      argumentIndex++;
+    private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
+        InvokeMethod invoke,
+        ProgramMethod resolvedMethod,
+        ProgramMethod singleTarget,
+        ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+      return computeMonomorphicMethodState(
+          invoke,
+          resolvedMethod,
+          singleTarget,
+          existingMethodState,
+          invoke.isInvokeMethodWithReceiver()
+              ? invoke.getFirstArgument().getDynamicType(appView)
+              : null);
     }
 
-    for (; argumentIndex < invoke.arguments().size(); argumentIndex++) {
-      parameterStates.add(
-          computeParameterStateForNonReceiver(
-              invoke,
-              singleTarget,
-              argumentIndex,
-              invoke.getArgument(argumentIndex),
-              abstractValueSupplier,
-              context,
-              existingMethodState));
+    @SuppressWarnings("UnusedVariable")
+    private ConcreteMonomorphicMethodStateOrUnknown computeMonomorphicMethodState(
+        InvokeMethod invoke,
+        ProgramMethod resolvedMethod,
+        ProgramMethod singleTarget,
+        ConcreteMonomorphicMethodStateOrBottom existingMethodState,
+        DynamicType dynamicReceiverType) {
+      List<ValueState> parameterStates = new ArrayList<>(invoke.arguments().size());
+
+      MethodReprocessingCriteria methodReprocessingCriteria =
+          singleTarget != null
+              ? reprocessingCriteriaCollection.getReprocessingCriteria(singleTarget)
+              : MethodReprocessingCriteria.alwaysReprocess();
+
+      int argumentIndex = 0;
+      if (invoke.isInvokeMethodWithReceiver()) {
+        assert dynamicReceiverType != null;
+        parameterStates.add(
+            computeParameterStateForReceiver(
+                resolvedMethod,
+                dynamicReceiverType,
+                existingMethodState,
+                methodReprocessingCriteria.getParameterReprocessingCriteria(0)));
+        argumentIndex++;
+      }
+
+      for (; argumentIndex < invoke.arguments().size(); argumentIndex++) {
+        parameterStates.add(
+            computeParameterStateForNonReceiver(
+                invoke,
+                singleTarget,
+                argumentIndex,
+                invoke.getArgument(argumentIndex),
+                existingMethodState));
+      }
+
+      // We simulate that the return value is used for methods with void return type. This ensures
+      // that we will widen the method state to unknown if/when all parameter states become unknown.
+      boolean isReturnValueUsed = invoke.getReturnType().isVoidType() || invoke.hasUsedOutValue();
+      return ConcreteMonomorphicMethodState.create(isReturnValueUsed, parameterStates);
     }
 
-    // We simulate that the return value is used for methods with void return type. This ensures
-    // that we will widen the method state to unknown if/when all parameter states become unknown.
-    boolean isReturnValueUsed = invoke.getReturnType().isVoidType() || invoke.hasUsedOutValue();
-    return ConcreteMonomorphicMethodState.create(isReturnValueUsed, parameterStates);
-  }
+    // For receivers there is not much point in trying to track an abstract value. Therefore we only
+    // track the dynamic type for receivers.
+    // TODO(b/190154391): Consider validating the above hypothesis by using
+    //  computeParameterStateForNonReceiver() for receivers.
+    private ValueState computeParameterStateForReceiver(
+        ProgramMethod resolvedMethod,
+        DynamicType dynamicReceiverType,
+        ConcreteMonomorphicMethodStateOrBottom existingMethodState,
+        ParameterReprocessingCriteria parameterReprocessingCriteria) {
+      // Don't compute a state for this parameter if the stored state is already unknown.
+      if (existingMethodState.isMonomorphic()
+          && existingMethodState.asMonomorphic().getParameterState(0).isUnknown()) {
+        return ValueState.unknown();
+      }
 
-  // For receivers there is not much point in trying to track an abstract value. Therefore we only
-  // track the dynamic type for receivers.
-  // TODO(b/190154391): Consider validating the above hypothesis by using
-  //  computeParameterStateForNonReceiver() for receivers.
-  private ValueState computeParameterStateForReceiver(
-      ProgramMethod resolvedMethod,
-      DynamicType dynamicReceiverType,
-      ConcreteMonomorphicMethodStateOrBottom existingMethodState,
-      ParameterReprocessingCriteria parameterReprocessingCriteria) {
-    // Don't compute a state for this parameter if the stored state is already unknown.
-    if (existingMethodState.isMonomorphic()
-        && existingMethodState.asMonomorphic().getParameterState(0).isUnknown()) {
-      return ValueState.unknown();
+      // For receivers we only track the dynamic type. Therefore, if there is no need to track the
+      // dynamic type of the receiver of the targeted method, then just return unknown.
+      if (!parameterReprocessingCriteria.shouldReprocessDueToDynamicType()) {
+        return ValueState.unknown();
+      }
+
+      DynamicType widenedDynamicReceiverType =
+          WideningUtils.widenDynamicReceiverType(appView, resolvedMethod, dynamicReceiverType);
+      return widenedDynamicReceiverType.isUnknown()
+          ? ValueState.unknown()
+          : new ConcreteReceiverValueState(dynamicReceiverType);
     }
 
-    // For receivers we only track the dynamic type. Therefore, if there is no need to track the
-    // dynamic type of the receiver of the targeted method, then just return unknown.
-    if (!parameterReprocessingCriteria.shouldReprocessDueToDynamicType()) {
-      return ValueState.unknown();
+    @SuppressWarnings("UnusedVariable")
+    private ValueState computeParameterStateForNonReceiver(
+        InvokeMethod invoke,
+        ProgramMethod singleTarget,
+        int argumentIndex,
+        Value argument,
+        ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
+      ValueState modeledState =
+          modeling.modelParameterStateForArgumentToFunction(
+              invoke, singleTarget, argumentIndex, argument, context);
+      if (modeledState != null) {
+        return modeledState;
+      }
+
+      // Don't compute a state for this parameter if the stored state is already unknown.
+      if (existingMethodState.isMonomorphic()
+          && existingMethodState.asMonomorphic().getParameterState(argumentIndex).isUnknown()) {
+        return ValueState.unknown();
+      }
+
+      DexType parameterType =
+          invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic());
+
+      // If the value is an argument of the enclosing method, then clearly we have no information
+      // about its abstract value. Instead of treating this as having an unknown runtime value, we
+      // instead record a flow constraint that specifies that all values that flow into the
+      // parameter of this enclosing method also flows into the corresponding parameter of the
+      // methods potentially called from this invoke instruction.
+      NonEmptyValueState inFlowState = computeInFlowState(parameterType, argument, context);
+      if (inFlowState != null) {
+        return inFlowState;
+      }
+
+      // Only track the nullability for array types.
+      if (parameterType.isArrayType()) {
+        Nullability nullability = argument.getType().nullability();
+        return ConcreteArrayTypeValueState.create(nullability);
+      }
+
+      AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(argument);
+
+      // For class types, we track both the abstract value and the dynamic type. If both are
+      // unknown, then use UnknownParameterState.
+      if (parameterType.isClassType()) {
+        DynamicType dynamicType = argument.getDynamicType(appView);
+        DynamicType widenedDynamicType =
+            WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
+        return ConcreteClassTypeValueState.create(abstractValue, widenedDynamicType);
+      } else {
+        // For primitive types, we only track the abstract value, thus if the abstract value is
+        // unknown, we use UnknownParameterState.
+        assert parameterType.isPrimitiveType();
+        return ConcretePrimitiveTypeValueState.create(abstractValue);
+      }
     }
 
-    DynamicType widenedDynamicReceiverType =
-        WideningUtils.widenDynamicReceiverType(appView, resolvedMethod, dynamicReceiverType);
-    return widenedDynamicReceiverType.isUnknown()
-        ? ValueState.unknown()
-        : new ConcreteReceiverValueState(dynamicReceiverType);
-  }
+    @SuppressWarnings("ReferenceEquality")
+    private DexMethod getRepresentative(InvokeMethod invoke, ProgramMethod resolvedMethod) {
+      if (resolvedMethod.getDefinition().belongsToDirectPool()) {
+        return resolvedMethod.getReference();
+      }
 
-  @SuppressWarnings("UnusedVariable")
-  private ValueState computeParameterStateForNonReceiver(
-      InvokeMethod invoke,
-      ProgramMethod singleTarget,
-      int argumentIndex,
-      Value argument,
-      AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
-      ConcreteMonomorphicMethodStateOrBottom existingMethodState) {
-    ValueState modeledState =
-        modeling.modelParameterStateForArgumentToFunction(
-            invoke, singleTarget, argumentIndex, argument, context);
-    if (modeledState != null) {
-      return modeledState;
+      if (isMonomorphicVirtualMethod(resolvedMethod)) {
+        return resolvedMethod.getReference();
+      }
+
+      if (invoke.isInvokeInterface()) {
+        assert !isMonomorphicVirtualMethod(resolvedMethod);
+        return getVirtualRootMethod(resolvedMethod);
+      }
+
+      assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
+
+      DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
+      assert rootMethod != null;
+      assert !isMonomorphicVirtualMethod(resolvedMethod)
+          || rootMethod == resolvedMethod.getReference();
+      return rootMethod;
     }
 
-    // Don't compute a state for this parameter if the stored state is already unknown.
-    if (existingMethodState.isMonomorphic()
-        && existingMethodState.asMonomorphic().getParameterState(argumentIndex).isUnknown()) {
-      return ValueState.unknown();
+    private boolean shouldUsePolymorphicMethodState(
+        InvokeMethod invoke, ProgramMethod resolvedMethod) {
+      return !resolvedMethod.getDefinition().belongsToDirectPool()
+          && !isMonomorphicVirtualMethod(getRepresentative(invoke, resolvedMethod));
     }
 
-    DexType parameterType =
-        invoke.getInvokedMethod().getArgumentType(argumentIndex, invoke.isInvokeStatic());
-
-    // If the value is an argument of the enclosing method, then clearly we have no information
-    // about its abstract value. Instead of treating this as having an unknown runtime value, we
-    // instead record a flow constraint that specifies that all values that flow into the parameter
-    // of this enclosing method also flows into the corresponding parameter of the methods
-    // potentially called from this invoke instruction.
-    NonEmptyValueState inFlowState = computeInFlowState(parameterType, argument, context);
-    if (inFlowState != null) {
-      return inFlowState;
-    }
-
-    // Only track the nullability for array types.
-    if (parameterType.isArrayType()) {
-      Nullability nullability = argument.getType().nullability();
-      return ConcreteArrayTypeValueState.create(nullability);
-    }
-
-    AbstractValue abstractValue = abstractValueSupplier.getAbstractValue(argument);
-
-    // For class types, we track both the abstract value and the dynamic type. If both are unknown,
-    // then use UnknownParameterState.
-    if (parameterType.isClassType()) {
-      DynamicType dynamicType = argument.getDynamicType(appView);
-      DynamicType widenedDynamicType =
-          WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
-      return ConcreteClassTypeValueState.create(abstractValue, widenedDynamicType);
-    } else {
-      // For primitive types, we only track the abstract value, thus if the abstract value is
-      // unknown,
-      // we use UnknownParameterState.
-      assert parameterType.isPrimitiveType();
-      return ConcretePrimitiveTypeValueState.create(abstractValue);
-    }
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private DexMethod getRepresentative(InvokeMethod invoke, ProgramMethod resolvedMethod) {
-    if (resolvedMethod.getDefinition().belongsToDirectPool()) {
-      return resolvedMethod.getReference();
-    }
-
-    if (isMonomorphicVirtualMethod(resolvedMethod)) {
-      return resolvedMethod.getReference();
-    }
-
-    if (invoke.isInvokeInterface()) {
-      assert !isMonomorphicVirtualMethod(resolvedMethod);
-      return getVirtualRootMethod(resolvedMethod);
-    }
-
-    assert invoke.isInvokeSuper() || invoke.isInvokeVirtual();
-
-    DexMethod rootMethod = getVirtualRootMethod(resolvedMethod);
-    assert rootMethod != null;
-    assert !isMonomorphicVirtualMethod(resolvedMethod)
-        || rootMethod == resolvedMethod.getReference();
-    return rootMethod;
-  }
-
-  private boolean shouldUsePolymorphicMethodState(
-      InvokeMethod invoke, ProgramMethod resolvedMethod) {
-    return !resolvedMethod.getDefinition().belongsToDirectPool()
-        && !isMonomorphicVirtualMethod(getRepresentative(invoke, resolvedMethod));
-  }
-
-  private void scan(InvokeCustom invoke) {
-    // If the bootstrap method is program declared it will be called. The call is with runtime
-    // provided arguments so ensure that the argument information is unknown.
-    DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
-    SingleResolutionResult<?> resolution =
-        appView
-            .appInfo()
-            .resolveMethodLegacy(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
-            .asSingleResolution();
-    if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
-      methodStates.set(resolution.getResolvedProgramMethod(), UnknownMethodState.get());
+    private void scanInvokeCustom(InvokeCustom invoke) {
+      // If the bootstrap method is program declared it will be called. The call is with runtime
+      // provided arguments so ensure that the argument information is unknown.
+      DexMethodHandle bootstrapMethod = invoke.getCallSite().bootstrapMethod;
+      SingleResolutionResult<?> resolution =
+          appView
+              .appInfo()
+              .resolveMethodLegacy(bootstrapMethod.asMethod(), bootstrapMethod.isInterface)
+              .asSingleResolution();
+      if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+        methodStates.set(resolution.getResolvedProgramMethod(), UnknownMethodState.get());
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
index 174e8ba..2116f3a 100644
--- a/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
+++ b/src/main/java/com/android/tools/r8/optimize/compose/ArgumentPropagatorCodeScannerForComposableFunctions.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.AbstractValueSupplier;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorCodeScanner;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodParameter;
@@ -25,16 +26,12 @@
   }
 
   @Override
-  protected void addTemporaryMethodState(
-      InvokeMethod invoke,
-      ProgramMethod resolvedMethod,
+  public void scan(
+      ProgramMethod method,
+      IRCode code,
       AbstractValueSupplier abstractValueSupplier,
-      ProgramMethod context,
       Timing timing) {
-    ComposableCallGraphNode node = callGraph.getNodes().get(resolvedMethod);
-    if (node != null && node.isComposable()) {
-      super.addTemporaryMethodState(invoke, resolvedMethod, abstractValueSupplier, context, timing);
-    }
+    new CodeScanner(abstractValueSupplier, code, method).scan(timing);
   }
 
   @Override
@@ -43,4 +40,21 @@
     // We haven't defined the virtual root mapping, so we can't tell.
     return false;
   }
+
+  private class CodeScanner extends ArgumentPropagatorCodeScanner.CodeScanner {
+
+    protected CodeScanner(
+        AbstractValueSupplier abstractValueSupplier, IRCode code, ProgramMethod method) {
+      super(abstractValueSupplier, code, method);
+    }
+
+    @Override
+    protected void addTemporaryMethodState(
+        InvokeMethod invoke, ProgramMethod resolvedMethod, Timing timing) {
+      ComposableCallGraphNode node = callGraph.getNodes().get(resolvedMethod);
+      if (node != null && node.isComposable()) {
+        super.addTemporaryMethodState(invoke, resolvedMethod, timing);
+      }
+    }
+  }
 }