Add possible targets for invokes with dynamic dispatch to call graph

Bug: 131572881
Change-Id: Ib154f89b7cc558fa01137d6f37ed3b070ab4e49c
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
index 15aefa7..28a6b84 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilder.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.logging.Log;
@@ -36,6 +36,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -44,6 +45,8 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
+  private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
+      new ConcurrentHashMap<>();
 
   CallGraphBuilder(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
@@ -128,54 +131,66 @@
 
     private void addTarget(DexEncodedMethod callee) {
       if (!callee.accessFlags.isAbstract()) {
+        assert callee.isProgramMethod(appView);
         getOrCreateNode(callee).addCallerConcurrently(caller);
       }
     }
 
-    private void addPossibleTarget(DexEncodedMethod possibleTarget) {
-      DexClass possibleTargetClass = appView.definitionFor(possibleTarget.method.holder);
-      if (possibleTargetClass != null && possibleTargetClass.isProgramClass()) {
-        addTarget(possibleTarget);
-      }
-    }
-
-    private void addPossibleTargets(
-        DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
-      for (DexEncodedMethod possibleTarget : possibleTargets) {
-        if (possibleTarget != definition) {
-          addPossibleTarget(possibleTarget);
+    private void processInvoke(Type originalType, DexMethod originalMethod) {
+      DexEncodedMethod source = caller.method;
+      DexMethod context = source.method;
+      GraphLenseLookupResult result =
+          appView.graphLense().lookupMethod(originalMethod, context, originalType);
+      DexMethod method = result.getMethod();
+      Type type = result.getType();
+      if (type == Type.INTERFACE || type == Type.VIRTUAL) {
+        // For virtual and interface calls add all potential targets that could be called.
+        ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+        resolutionResult.forEachTarget(target -> processInvokeWithDynamicDispatch(type, target));
+      } else {
+        DexEncodedMethod singleTarget =
+            appView.appInfo().lookupSingleTarget(type, method, context.holder);
+        if (singleTarget != null) {
+          assert !source.accessFlags.isBridge() || singleTarget != caller.method;
+          DexClass clazz = appView.definitionFor(singleTarget.method.holder);
+          assert clazz != null;
+          if (clazz.isProgramClass()) {
+            // For static invokes, the class could be initialized.
+            if (type == Type.STATIC) {
+              addClassInitializerTarget(clazz);
+            }
+            addTarget(singleTarget);
+          }
         }
       }
     }
 
-    private void processInvoke(Type type, DexMethod method) {
-      DexEncodedMethod source = caller.method;
-      GraphLenseLookupResult result =
-          appView.graphLense().lookupMethod(method, source.method, type);
-      method = result.getMethod();
-      type = result.getType();
-      DexEncodedMethod definition =
-          appView.appInfo().lookupSingleTarget(type, method, source.method.holder);
-      if (definition != null) {
-        assert !source.accessFlags.isBridge() || definition != caller.method;
-        DexClass clazz = appView.definitionFor(definition.method.holder);
-        assert clazz != null;
-        if (clazz.isProgramClass()) {
-          // For static invokes, the class could be initialized.
-          if (type == Invoke.Type.STATIC) {
-            addClassInitializerTarget(clazz);
-          }
+    private void processInvokeWithDynamicDispatch(Type type, DexEncodedMethod encodedTarget) {
+      DexMethod target = encodedTarget.method;
+      DexClass clazz = appView.definitionFor(target.holder);
+      if (clazz == null) {
+        assert false : "Unable to lookup holder of `" + target.toSourceString() + "`";
+        return;
+      }
 
-          addTarget(definition);
-          // For virtual and interface calls add all potential targets that could be called.
-          if (type == Invoke.Type.VIRTUAL || type == Invoke.Type.INTERFACE) {
-            Set<DexEncodedMethod> possibleTargets;
-            if (clazz.isInterface()) {
-              possibleTargets = appView.appInfo().lookupInterfaceTargets(definition.method);
-            } else {
-              possibleTargets = appView.appInfo().lookupVirtualTargets(definition.method);
-            }
-            addPossibleTargets(definition, possibleTargets);
+      if (!appView.options().testing.addCallEdgesForLibraryInvokes) {
+        if (clazz.isLibraryClass()) {
+          // Likely to have many possible targets.
+          return;
+        }
+      }
+
+      Set<DexEncodedMethod> possibleTargets =
+          possibleTargetsCache.computeIfAbsent(
+              target,
+              method ->
+                  type == Type.INTERFACE
+                      ? appView.appInfo().lookupInterfaceTargets(method)
+                      : appView.appInfo().lookupVirtualTargets(method));
+      if (possibleTargets != null) {
+        for (DexEncodedMethod possibleTarget : possibleTargets) {
+          if (possibleTarget.isProgramMethod(appView)) {
+            addTarget(possibleTarget);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index eb5f870..25dd6e8 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -599,6 +599,21 @@
             ? NondeterministicIROrdering.getInstance()
             : IdentityIROrdering.getInstance();
 
+    /**
+     * If this flag is enabled, we will also compute the set of possible targets for invoke-
+     * interface and invoke-virtual instructions that target a library method, and add the
+     * corresponding edges to the call graph.
+     *
+     * <p>Setting this flag leads to more call graph edges, which can be good for size (e.g., it
+     * increases the likelihood that virtual methods have been processed by the time their call
+     * sites are processed, which allows more inlining).
+     *
+     * <p>However, the set of possible targets for such invokes can be very large. As an example,
+     * consider the instruction {@code invoke-virtual {v0, v1}, `void Object.equals(Object)`}).
+     * Therefore, tracing such invokes comes at a considerable performance penalty.
+     */
+    public boolean addCallEdgesForLibraryInvokes = false;
+
     public boolean allowProguardRulesThatUseExtendsOrImplementsWrong = true;
     public boolean allowTypeErrors =
         !Version.isDev() || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java
index b60eff8..a0ed3e6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.optimize.inliner;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -68,43 +67,24 @@
     assertThat(testClassSubject.mainMethod(), isPresent());
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
-    if (invokeMethodOnA) {
-      // TODO(b/131572881): Should not inline A.m() since it has many call sites.
-      assertThat(aClassSubject, not(isPresent()));
-    } else {
-      assertThat(aClassSubject, isPresent());
-    }
+    assertThat(aClassSubject, isPresent());
 
-    if (invokeMethodOnA) {
-      // TODO(b/131572881): Should not inline A.m() since it has many call sites.
-      assertEquals(
-          5,
-          testClassSubject
-              .mainMethod()
-              .streamInstructions()
-              .filter(
-                  instruction ->
-                      instruction.isInvokeVirtual()
-                          && instruction.getMethod().name.toSourceString().equals("println"))
-              .count());
-    } else {
-      MethodSubject methodSubject = aClassSubject.uniqueMethodWithName("m");
-      assertThat(methodSubject, isPresent());
-      assertEquals(
-          5,
-          testClassSubject
-              .mainMethod()
-              .streamInstructions()
-              .filter(
-                  instruction ->
-                      instruction.isInvokeVirtual()
-                          && instruction
-                              .getMethod()
-                              .name
-                              .toSourceString()
-                              .equals(methodSubject.getFinalName()))
-              .count());
-    }
+    MethodSubject methodSubject = aClassSubject.uniqueMethodWithName("m");
+    assertThat(methodSubject, isPresent());
+    assertEquals(
+        5,
+        testClassSubject
+            .mainMethod()
+            .streamInstructions()
+            .filter(
+                instruction ->
+                    instruction.isInvokeVirtual()
+                        && instruction
+                            .getMethod()
+                            .name
+                            .toSourceString()
+                            .equals(methodSubject.getFinalName()))
+            .count());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 0df662c..8d975f1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -65,139 +67,180 @@
   public void testJStyleLambdas() throws Exception {
     assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_lambda_j_style.MainKt";
-    runTest("class_inliner_lambda_j_style", mainClassName, false, (app) -> {
-      CodeInspector inspector = new CodeInspector(app);
-      assertTrue(
-          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
-      assertTrue(
-          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1").isPresent());
-      assertTrue(
-          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1").isPresent());
-    });
+    runTest(
+        "class_inliner_lambda_j_style",
+        mainClassName,
+        false,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          assertThat(
+              inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"), isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"), isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"), isPresent());
+        });
 
-    runTest("class_inliner_lambda_j_style", mainClassName, true, (app) -> {
-      CodeInspector inspector = new CodeInspector(app);
-      Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
-      ClassSubject clazz = inspector.clazz(mainClassName);
+    runTest(
+        "class_inliner_lambda_j_style",
+        mainClassName,
+        true,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
+          ClassSubject clazz = inspector.clazz(mainClassName);
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz, "testStateless"));
+          assertEquals(
+              Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateless"));
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz, "testStateful"));
+          assertEquals(Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful"));
 
-      assertFalse(
-          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
+              not(isPresent()));
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz, "testStateful2"));
+          assertEquals(
+              Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful2"));
 
-      assertFalse(
-          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1").isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1"),
+              not(isPresent()));
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz, "testStateful3"));
+          assertEquals(
+              Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testStateful3"));
 
-      assertFalse(
-          inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1").isPresent());
-    });
+          assertThat(
+              inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1"),
+              not(isPresent()));
+        });
   }
 
   @Test
   public void testKStyleLambdas() throws Exception {
     assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_lambda_k_style.MainKt";
-    runTest("class_inliner_lambda_k_style", mainClassName, false, (app) -> {
-      CodeInspector inspector = new CodeInspector(app);
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1").isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1").isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
-          .isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1")
-          .isPresent());
-      assertTrue(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1")
-          .isPresent());
-    });
+    runTest(
+        "class_inliner_lambda_k_style",
+        mainClassName,
+        false,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz(
+                  "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz(
+                  "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"),
+              isPresent());
+          assertThat(
+              inspector.clazz(
+                  "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"),
+              isPresent());
+        });
 
-    runTest("class_inliner_lambda_k_style", mainClassName, true, (app) -> {
-      CodeInspector inspector = new CodeInspector(app);
-      Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
-      ClassSubject clazz = inspector.clazz(mainClassName);
+    runTest(
+        "class_inliner_lambda_k_style",
+        mainClassName,
+        true,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
+          ClassSubject clazz = inspector.clazz(mainClassName);
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz,
-              "testKotlinSequencesStateless", "kotlin.sequences.Sequence"));
+          assertEquals(
+              Sets.newHashSet(),
+              collectAccessedTypes(
+                  lambdaCheck, clazz, "testKotlinSequencesStateless", "kotlin.sequences.Sequence"));
 
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1"),
+              not(isPresent()));
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz,
-              "testKotlinSequencesStateful", "int", "int", "kotlin.sequences.Sequence"));
+          assertEquals(
+              Sets.newHashSet(),
+              collectAccessedTypes(
+                  lambdaCheck,
+                  clazz,
+                  "testKotlinSequencesStateful",
+                  "int",
+                  "int",
+                  "kotlin.sequences.Sequence"));
 
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1").isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateful$1"),
+              not(isPresent()));
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod"));
+          assertEquals(
+              Sets.newHashSet(), collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod"));
 
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1").isPresent());
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1").isPresent());
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
+              not(isPresent()));
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod2$1"),
+              not(isPresent()));
+          assertThat(
+              inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod3$1"),
+              not(isPresent()));
 
-      assertEquals(
-          Sets.newHashSet(),
-          collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
+          assertEquals(
+              Sets.newHashSet(),
+              collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
 
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
-          .isPresent());
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1")
-          .isPresent());
-      assertFalse(inspector.clazz(
-          "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1")
-          .isPresent());
-    });
+          assertThat(
+              inspector.clazz(
+                  "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1"),
+              not(isPresent()));
+          assertThat(
+              inspector.clazz(
+                  "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda2$1"),
+              not(isPresent()));
+          assertThat(
+              inspector.clazz(
+                  "class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda3$1"),
+              not(isPresent()));
+        });
   }
 
   @Test
   public void testDataClass() throws Exception {
     assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_data_class.MainKt";
-    runTest("class_inliner_data_class", mainClassName, true, (app) -> {
-      CodeInspector inspector = new CodeInspector(app);
-      ClassSubject clazz = inspector.clazz(mainClassName);
-      assertTrue(collectAccessedTypes(
-          type -> !type.toSourceString().startsWith("java."),
-          clazz, "main", String[].class.getCanonicalName()).isEmpty());
-      assertEquals(
-          Lists.newArrayList(
-              "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"
-          ),
-          collectStaticCalls(clazz, "main", String[].class.getCanonicalName()));
-    });
+    runTest(
+        "class_inliner_data_class",
+        mainClassName,
+        true,
+        app -> {
+          CodeInspector inspector = new CodeInspector(app);
+          ClassSubject clazz = inspector.clazz(mainClassName);
+          assertTrue(
+              collectAccessedTypes(
+                      type -> !type.toSourceString().startsWith("java."),
+                      clazz,
+                      "main",
+                      String[].class.getCanonicalName())
+                  .isEmpty());
+          assertEquals(
+              Lists.newArrayList(
+                  "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"),
+              collectStaticCalls(clazz, "main", String[].class.getCanonicalName()));
+        });
   }
 
   private Set<String> collectAccessedTypes(Predicate<DexType> isTypeOfInterest,
@@ -219,21 +262,27 @@
   protected void runTest(String folder, String mainClass,
       boolean enabled, AndroidAppInspector inspector) throws Exception {
     runTest(
-        folder, mainClass,
+        folder,
+        mainClass,
         // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
         StringUtils.lines(
             "-neverinline class * { void test*State*(...); }",
             "-neverinline class * { void testBigExtraMethod(...); }",
-            "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"
-        ),
+            "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"),
         options -> {
           options.enableInlining = true;
           options.enableClassInlining = enabled;
           options.enableLambdaMerging = false;
+
           // Tests check if specific lambdas are inlined or not, where some of target lambdas have
           // at least 4 instructions.
           options.inliningInstructionLimit = 4;
-        }, inspector);
+
+          // Class inlining depends on the processing order. We therefore insert all call graph
+          // edges and verify that we can class inline everything under this condition.
+          options.testing.addCallEdgesForLibraryInvokes = true;
+        },
+        inspector);
   }
 
   private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) {