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) {