Move utilities for looking up virtual targets to resolution result.
Bug: 140016938
Change-Id: I9d0a46b5d4440c99df1401b0a8e6db3f85d5db64
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 543326a..393a4fd 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -9,10 +9,12 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@@ -299,6 +301,9 @@
*/
public ResolutionResult resolveMethodOnClass(DexType holder, DexMethod method) {
assert checkIfObsolete();
+ if (holder.isArrayType()) {
+ return resolveMethodOnArray(holder, method);
+ }
DexClass clazz = definitionFor(holder);
// Step 1: If holder is an interface, resolution fails with an ICCE. We return null.
if (clazz == null || clazz.isInterface()) {
@@ -605,6 +610,96 @@
List<DexEncodedMethod> asListOfTargets();
void forEachTarget(Consumer<DexEncodedMethod> consumer);
+
+ default Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+ // TODO(b/140016938): Don't allow this lookup on non-virtual resolutions.
+ // First add the target for receiver type method.type.
+ Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+ forEachTarget(result::add);
+ // Add all matching targets from the subclass hierarchy.
+ DexEncodedMethod encodedMethod = asResultOfResolve();
+ DexMethod method = encodedMethod.method;
+ for (DexType type : appInfo.subtypes(method.holder)) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (!clazz.isInterface()) {
+ ResolutionResult methods = appInfo.resolveMethodOnClass(type, method);
+ methods.forEachTarget(
+ target -> {
+ if (target.isVirtualMethod()) {
+ result.add(target);
+ }
+ });
+ }
+ }
+ return result;
+ }
+
+ default Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ // TODO(b/140016938): Don't allow this lookup on non-virtual resolutions.
+ Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+ if (hasSingleTarget()) {
+ // Add default interface methods to the list of targets.
+ //
+ // This helps to make sure we take into account synthesized lambda classes
+ // that we are not aware of. Like in the following example, we know that all
+ // classes, XX in this case, override B::bar(), but there are also synthesized
+ // classes for lambda which don't, so we still need default method to be live.
+ //
+ // public static void main(String[] args) {
+ // X x = () -> {};
+ // x.bar();
+ // }
+ //
+ // interface X {
+ // void foo();
+ // default void bar() { }
+ // }
+ //
+ // class XX implements X {
+ // public void foo() { }
+ // public void bar() { }
+ // }
+ //
+ DexEncodedMethod singleTarget = asSingleTarget();
+ if (singleTarget.getCode() != null
+ && appInfo.hasAnyInstantiatedLambdas(singleTarget.method.holder)) {
+ result.add(singleTarget);
+ }
+ }
+
+ DexEncodedMethod encodedMethod = asResultOfResolve();
+ DexMethod method = encodedMethod.method;
+ Consumer<DexEncodedMethod> addIfNotAbstract =
+ m -> {
+ if (!m.accessFlags.isAbstract()) {
+ result.add(m);
+ }
+ };
+ // Default methods are looked up when looking at a specific subtype that does not override
+ // them.
+ // Otherwise, we would look up default methods that are actually never used. However, we have
+ // to
+ // add bridge methods, otherwise we can remove a bridge that will be used.
+ Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
+ m -> {
+ if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
+ result.add(m);
+ }
+ };
+
+ Set<DexType> set = appInfo.subtypes(method.holder);
+ for (DexType type : set) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz.isInterface()) {
+ ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(type, method);
+ targetMethods.forEachTarget(addIfNotAbstractAndBridge);
+ } else {
+ ResolutionResult targetMethods = appInfo.resolveMethodOnClass(type, method);
+ targetMethods.forEachTarget(addIfNotAbstract);
+ }
+ }
+ return result;
+ }
}
private static class MultiResultBuilder {
@@ -706,5 +801,15 @@
public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
// Intentionally left empty.
}
+
+ @Override
+ public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+ return null;
+ }
+
+ @Override
+ public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+ return null;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index e143dee..82dbe10 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -254,43 +254,6 @@
return true;
}
- // For mapping invoke virtual instruction to target methods.
- public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
- assert checkIfObsolete();
- if (method.holder.isArrayType()) {
- // For javac output this will only be clone(), but in general the methods from Object can
- // be invoked with an array type holder.
- return null;
- }
- DexClass root = definitionFor(method.holder);
- if (root == null) {
- // type specified in method does not have a materialized class.
- return null;
- }
- ResolutionResult topTargets = resolveMethodOnClass(method.holder, method);
- if (topTargets.asResultOfResolve() == null) {
- // This will fail at runtime.
- return null;
- }
- // First add the target for receiver type method.type.
- Set<DexEncodedMethod> result = new HashSet<>();
- topTargets.forEachTarget(result::add);
- // Add all matching targets from the subclass hierarchy.
- for (DexType type : subtypes(method.holder)) {
- DexClass clazz = definitionFor(type);
- if (!clazz.isInterface()) {
- ResolutionResult methods = resolveMethodOnClass(type, method);
- methods.forEachTarget(
- target -> {
- if (target.isVirtualMethod()) {
- result.add(target);
- }
- });
- }
- }
- return result;
- }
-
/**
* Lookup super method following the super chain from the holder of {@code method}.
*
@@ -363,76 +326,6 @@
return false;
}
- // For mapping invoke interface instruction to target methods.
- public Set<DexEncodedMethod> lookupInterfaceTargets(DexMethod method) {
- assert checkIfObsolete();
- // First check that there is a target for this invoke-interface to hit. If there is none,
- // this will fail at runtime.
- ResolutionResult topTarget = resolveMethodOnInterface(method.holder, method);
- if (topTarget.asResultOfResolve() == null) {
- return null;
- }
-
- Set<DexEncodedMethod> result = new HashSet<>();
- if (topTarget.hasSingleTarget()) {
- // Add default interface methods to the list of targets.
- //
- // This helps to make sure we take into account synthesized lambda classes
- // that we are not aware of. Like in the following example, we know that all
- // classes, XX in this case, override B::bar(), but there are also synthesized
- // classes for lambda which don't, so we still need default method to be live.
- //
- // public static void main(String[] args) {
- // X x = () -> {};
- // x.bar();
- // }
- //
- // interface X {
- // void foo();
- // default void bar() { }
- // }
- //
- // class XX implements X {
- // public void foo() { }
- // public void bar() { }
- // }
- //
- DexEncodedMethod singleTarget = topTarget.asSingleTarget();
- if (singleTarget.getCode() != null && hasAnyInstantiatedLambdas(singleTarget.method.holder)) {
- result.add(singleTarget);
- }
- }
-
- Consumer<DexEncodedMethod> addIfNotAbstract =
- m -> {
- if (!m.accessFlags.isAbstract()) {
- result.add(m);
- }
- };
- // Default methods are looked up when looking at a specific subtype that does not override them.
- // Otherwise, we would look up default methods that are actually never used. However, we have to
- // add bridge methods, otherwise we can remove a bridge that will be used.
- Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
- m -> {
- if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
- result.add(m);
- }
- };
-
- Set<DexType> set = subtypes(method.holder);
- for (DexType type : set) {
- DexClass clazz = definitionFor(type);
- if (clazz.isInterface()) {
- ResolutionResult targetMethods = resolveMethodOnInterface(type, method);
- targetMethods.forEachTarget(addIfNotAbstractAndBridge);
- } else {
- ResolutionResult targetMethods = resolveMethodOnClass(type, method);
- targetMethods.forEachTarget(addIfNotAbstract);
- }
- }
- return result;
- }
-
/**
* Resolve the methods implemented by the lambda expression that created the {@code callSite}.
*
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8bd6e4b..5a223ac 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1855,5 +1855,4 @@
checkIfObsolete();
consumer.accept(this);
}
-
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 6a6d5e3..45716d0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -100,7 +100,11 @@
@Override
public Collection<DexEncodedMethod> lookupTargets(
AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
- return appView.appInfo().lookupInterfaceTargets(getInvokedMethod());
+ DexMethod method = getInvokedMethod();
+ return appView
+ .appInfo()
+ .resolveMethodOnInterface(method.holder, method)
+ .lookupInterfaceTargets(appView.appInfo());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index a57c585..8a9caba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -103,7 +103,11 @@
@Override
public Collection<DexEncodedMethod> lookupTargets(
AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
- return appView.appInfo().lookupVirtualTargets(getInvokedMethod());
+ DexMethod method = getInvokedMethod();
+ return appView
+ .appInfo()
+ .resolveMethodOnClass(method.holder, method)
+ .lookupVirtualTargets(appView.appInfo());
}
@Override
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 d2aa749..dfaa803 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
@@ -189,8 +189,14 @@
target,
method ->
type == Type.INTERFACE
- ? appView.appInfo().lookupInterfaceTargets(method)
- : appView.appInfo().lookupVirtualTargets(method));
+ ? appView
+ .appInfo()
+ .resolveMethodOnInterface(method.holder, method)
+ .lookupInterfaceTargets(appView.appInfo())
+ : appView
+ .appInfo()
+ .resolveMethodOnClass(method.holder, method)
+ .lookupVirtualTargets(appView.appInfo()));
if (possibleTargets != null) {
boolean likelySpuriousCallEdge =
possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 8dc2df2..9e69123 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -163,7 +163,12 @@
public ConstraintWithTarget forInvokeInterface(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forVirtualInvoke(
- lookup, appView.appInfo().lookupInterfaceTargets(lookup), invocationContext);
+ lookup,
+ appView
+ .appInfo()
+ .resolveMethodOnInterface(lookup.holder, lookup)
+ .lookupInterfaceTargets(appView.appInfo()),
+ invocationContext);
}
public ConstraintWithTarget forInvokeMultiNewArray(DexType type, DexType invocationContext) {
@@ -192,7 +197,12 @@
public ConstraintWithTarget forInvokeVirtual(DexMethod method, DexType invocationContext) {
DexMethod lookup = graphLense.lookupMethod(method);
return forVirtualInvoke(
- lookup, appView.appInfo().lookupVirtualTargets(lookup), invocationContext);
+ lookup,
+ appView
+ .appInfo()
+ .resolveMethodOnClass(lookup.holder, lookup)
+ .lookupVirtualTargets(appView.appInfo()),
+ invocationContext);
}
public ConstraintWithTarget forJumpInstruction() {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 90dbcff5..ac9f78e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1623,12 +1623,11 @@
return;
}
- // Lookup the possible targets starting from the declared type.
assert interfaceInvoke == holder.isInterface();
Set<DexEncodedMethod> possibleTargets =
holder.isInterface()
- ? appInfo.lookupInterfaceTargets(method)
- : appInfo.lookupVirtualTargets(method);
+ ? resolutionTarget.lookupInterfaceTargets(appInfo)
+ : resolutionTarget.lookupVirtualTargets(appInfo);
if (possibleTargets == null || possibleTargets.isEmpty()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 7892482..1dca48a 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -753,7 +753,10 @@
// }
for (DexEncodedMethod method : defaultMethods) {
// Conservatively find all possible targets for this method.
- Set<DexEncodedMethod> interfaceTargets = appInfo.lookupInterfaceTargets(method.method);
+ Set<DexEncodedMethod> interfaceTargets =
+ appInfo
+ .resolveMethodOnInterface(method.method.holder, method.method)
+ .lookupInterfaceTargets(appInfo);
// If [method] is not even an interface-target, then we can safely merge it. Otherwise we
// need to check for a conflict.
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
index b451b48..e9e836b 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
@@ -59,12 +59,18 @@
assertEquals(appInfo.lookupVirtualTarget(id.holder, method.method), method);
// Check lookup targets with include method.
- Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(method.method);
+ Set<DexEncodedMethod> targets =
+ appInfo
+ .resolveMethodOnClass(method.method.holder, method.method)
+ .lookupVirtualTargets(appInfo);
assertTrue(targets.contains(method));
}
private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
- Set<DexEncodedMethod> targets = appInfo.lookupInterfaceTargets(method.method);
+ Set<DexEncodedMethod> targets =
+ appInfo
+ .resolveMethodOnInterface(method.method.holder, method.method)
+ .lookupInterfaceTargets(appInfo);
if (appInfo.subtypes(method.method.holder).stream()
.allMatch(t -> appInfo.definitionFor(t).isInterface())) {
assertEquals(
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 47aa023..11265a9 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -247,7 +247,8 @@
DexMethod method = buildMethod(invokeReceiver, methodName, appInfo);
Assert.assertNotNull(
appInfo.resolveMethod(toType(invokeReceiver, appInfo), method).asResultOfResolve());
- Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(method);
+ Set<DexEncodedMethod> targets =
+ appInfo.resolveMethod(method.holder, method).lookupVirtualTargets(appInfo);
Set<DexType> targetHolders = targets.stream().map(m -> m.method.holder)
.collect(Collectors.toSet());
Assert.assertEquals(allTargetHolders.size(), targetHolders.size());
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
index 8b71aaf..c565a43 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -92,13 +93,13 @@
@Test
public void lookupVirtualTargets() {
- DexEncodedMethod resolved =
- appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ ResolutionResult resolutionResult = appInfo.resolveMethod(methodOnB.holder, methodOnB);
+ DexEncodedMethod resolved = resolutionResult.asResultOfResolve();
assertEquals(methodOnA, resolved.method);
// This behavior is up for debate as the resolution target is A.f which is private static, thus
// the runtime behavior will be to throw a runtime exception. Thus it would be reasonable to
// return the empty set as no targets can actually be hit at runtime.
- Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(methodOnB);
+ Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
assertTrue("Expected " + methodOnA, targets.stream().anyMatch(m -> m.method == methodOnA));
assertTrue("Expected " + methodOnC, targets.stream().anyMatch(m -> m.method == methodOnC));
}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index b7b328e..0ae734d 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -142,11 +143,11 @@
@Test
public void lookupVirtualTargets() {
- DexEncodedMethod resolved =
- appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ ResolutionResult resolutionResult = appInfo.resolveMethod(methodOnB.holder, methodOnB);
+ DexEncodedMethod resolved = resolutionResult.asResultOfResolve();
assertEquals(methodOnA, resolved.method);
// See comment in VirtualOverrideOfPrivateStaticMethodTest.lookupVirtualTargets().
- Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(methodOnB);
+ Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
assertTrue("Expected " + methodOnA, targets.stream().anyMatch(m -> m.method == methodOnA));
assertTrue("Expected " + methodOnC, targets.stream().anyMatch(m -> m.method == methodOnC));
}
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index d93d684..f7b72e9 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -176,11 +177,11 @@
@Test
public void lookupVirtualTargets() {
- DexEncodedMethod resolved =
- appInfo.resolveMethod(methodOnB.holder, methodOnB).asResultOfResolve();
+ ResolutionResult resolutionResult = appInfo.resolveMethod(methodOnB.holder, methodOnB);
+ DexEncodedMethod resolved = resolutionResult.asResultOfResolve();
assertEquals(methodOnA, resolved.method);
// See comment in VirtualOverrideOfPrivateStaticMethodTest.lookupVirtualTargets().
- Set<DexEncodedMethod> targets = appInfo.lookupVirtualTargets(methodOnB);
+ Set<DexEncodedMethod> targets = resolutionResult.lookupVirtualTargets(appInfo);
assertTrue("Expected " + methodOnA, targets.stream().anyMatch(m -> m.method == methodOnA));
assertTrue("Expected " + methodOnC, targets.stream().anyMatch(m -> m.method == methodOnC));
}