Lazily identify overridden library methods.
Bug: 139464956
Change-Id: I9d7146a4a0584b484417a820da5c5238d2c8c337
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 9838946..f4afbbe 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -47,7 +47,6 @@
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -99,7 +98,6 @@
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
-import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -181,10 +179,6 @@
private final Map<DexProgramClass, SetWithStoredReason<DexEncodedMethod>>
reachableVirtualMethods = Maps.newIdentityHashMap();
- // TODO(b/139464956): Lazily compute library dependencies.
- private final Map<DexProgramClass, Map<DexEncodedMethod, Set<DexType>>>
- reachableVirtualMethodsFromLibraries = Maps.newIdentityHashMap();
-
/**
* Tracks the dependency between a method and the super-method it calls, if any. Used to make
* super methods become live when they become reachable from a live sub-method.
@@ -1051,23 +1045,6 @@
if (reachableMethods != null) {
transitionNonAbstractMethodsToLiveAndShadow(clazz, reachableMethods, seen);
}
- Map<DexEncodedMethod, Set<DexType>> libraryMethods =
- reachableVirtualMethodsFromLibraries.get(clazz);
- if (libraryMethods == null) {
- return;
- }
- for (Entry<DexEncodedMethod, Set<DexType>> entry : libraryMethods.entrySet()) {
- DexEncodedMethod encodedMethod = entry.getKey();
- if (seen.addMethod(encodedMethod)) {
- // Abstract methods do shadow implementations but they cannot be live, as they have no
- // code.
- // TODO(b/120959039): The edge registration needs to be per interface.
- KeepReason reason = KeepReason.isLibraryMethod(clazz, entry.getValue().iterator().next());
- if (!encodedMethod.accessFlags.isAbstract()) {
- markVirtualMethodAsLive(clazz, encodedMethod, reason);
- }
- }
- }
}
private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
@@ -1485,6 +1462,9 @@
* are either defined directly on this type or that are defined on a supertype but are not
* shadowed by another inherited method. Furthermore, default methods from implemented interfaces
* that are not otherwise shadowed are considered, too.
+ *
+ * <p>Finally all methods on library types that resolve starting at the instantiated type are
+ * marked live.
*/
private void transitionMethodsForInstantiatedClass(DexProgramClass instantiatedClass) {
ScopedDexMethodSet seen = new ScopedDexMethodSet();
@@ -1498,6 +1478,7 @@
Collections.addAll(interfaces, current.interfaces.values);
current = getProgramClassOrNull(current.superType);
} while (current != null && !instantiatedTypes.contains(current));
+
// The set now contains all virtual methods on the type and its supertype that are reachable.
// In a second step, we now look at interfaces. We have to do this in this order due to JVM
// semantics for default methods. A default method is only reachable if it is not overridden in
@@ -1515,6 +1496,62 @@
}
transitionDefaultMethodsForInstantiatedClass(iface, seen);
}
+
+ // When a type becomes live, all library methods on that type become live too.
+ // This is done by searching the library supertypes and then resolving each method defined by
+ // such a library type from the point of the instantiated type. If the resolved targets are in
+ // the program, i.e., the instantiated type has a method overidding a library method, then the
+ // program method is live.
+ Deque<DexClass> librarySearchItems = new ArrayDeque<>();
+ librarySearchItems.add(instantiatedClass);
+ while (!librarySearchItems.isEmpty()) {
+ DexClass clazz = librarySearchItems.pop();
+ if (clazz.isNotProgramClass()) {
+ markLibraryAndClasspathMethodOverriddesAsLive(clazz, instantiatedClass);
+ }
+ if (clazz.superType != null) {
+ DexClass superClass = appView.definitionFor(clazz.superType);
+ if (superClass != null) {
+ librarySearchItems.add(superClass);
+ }
+ }
+ for (DexType iface : clazz.interfaces.values) {
+ DexClass ifaceClass = appView.definitionFor(iface);
+ if (ifaceClass != null) {
+ librarySearchItems.add(ifaceClass);
+ }
+ }
+ }
+ }
+
+ private void markLibraryAndClasspathMethodOverriddesAsLive(
+ DexClass libraryClass, DexProgramClass instantiatedClass) {
+ assert libraryClass.isNotProgramClass();
+ assert !instantiatedClass.isInterface() || instantiatedClass.accessFlags.isAnnotation();
+ for (DexEncodedMethod method : libraryClass.virtualMethods()) {
+ // Note: it may be worthwhile to add a resolution cache here. If so, it must till ensure
+ // that all library override edges are reported to the kept-graph consumer.
+ ResolutionResult resolution =
+ appView.appInfo().resolveMethod(instantiatedClass, method.method);
+ if (resolution.isValidVirtualTarget(options)) {
+ resolution.forEachTarget(
+ target -> {
+ if (!target.isAbstract()) {
+ DexClass targetHolder = appView.definitionFor(target.method.holder);
+ if (targetHolder != null && targetHolder.isProgramClass()) {
+ DexProgramClass programClass = targetHolder.asProgramClass();
+ if (shouldMarkLibraryMethodOverrideAsReachable(programClass, target)) {
+ target.setLibraryMethodOverride();
+ markVirtualMethodAsLive(
+ programClass,
+ target,
+ KeepReason.isLibraryMethod(programClass, libraryClass.type));
+ }
+ }
+ }
+ });
+ }
+ }
}
private void transitionDefaultMethodsForInstantiatedClass(
@@ -1748,16 +1785,14 @@
private void markVirtualMethodAsReachable(
DexMethod method, boolean interfaceInvoke, KeepReason reason) {
- markVirtualMethodAsReachable(method, interfaceInvoke, reason, (x, y) -> true, null, false);
+ markVirtualMethodAsReachable(method, interfaceInvoke, reason, (x, y) -> true);
}
private void markVirtualMethodAsReachable(
DexMethod method,
boolean interfaceInvoke,
KeepReason reason,
- BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter,
- Consumer<DexEncodedMethod> possibleTargetsConsumer,
- boolean isLibraryEnqueueing) {
+ BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter) {
if (!virtualTargetsMarkedAsReachable.add(method)) {
return;
}
@@ -1799,19 +1834,13 @@
markPossibleTargetsAsReachable(
resolutionTarget == encodedPossibleTarget ? reason : overridesReason,
possibleTargetsFilter,
- isLibraryEnqueueing ? method.holder : null,
encodedPossibleTarget);
}
-
- if (possibleTargetsConsumer != null) {
- possibleTargets.forEach(possibleTargetsConsumer);
- }
}
private void markPossibleTargetsAsReachable(
KeepReason reason,
BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter,
- DexType enqueueingLibraryHolder,
DexEncodedMethod encodedPossibleTarget) {
assert encodedPossibleTarget.isVirtualMethod() || options.canUseNestBasedAccess();
assert !encodedPossibleTarget.isAbstract();
@@ -1823,22 +1852,10 @@
if (!possibleTargetsFilter.test(clazz, encodedPossibleTarget)) {
return;
}
- if (enqueueingLibraryHolder != null) {
- Map<DexEncodedMethod, Set<DexType>> entry =
- reachableVirtualMethodsFromLibraries.computeIfAbsent(
- clazz, ignore -> Maps.newIdentityHashMap());
- Set<DexType> libraryTypes = entry.get(encodedPossibleTarget);
- if (libraryTypes != null) {
- libraryTypes.add(enqueueingLibraryHolder);
- return;
- }
- entry.put(encodedPossibleTarget, SetUtils.newIdentityHashSet(enqueueingLibraryHolder, 2));
- } else {
- SetWithStoredReason<DexEncodedMethod> reachable =
- reachableVirtualMethods.computeIfAbsent(clazz, ignore -> SetWithStoredReason.create());
- if (!reachable.add(encodedPossibleTarget, reason)) {
- return;
- }
+ SetWithStoredReason<DexEncodedMethod> reachable =
+ reachableVirtualMethods.computeIfAbsent(clazz, ignore -> SetWithStoredReason.create());
+ if (!reachable.add(encodedPossibleTarget, reason)) {
+ return;
}
// If the holder type is instantiated, the method is live. Otherwise check whether we find
@@ -2043,10 +2060,6 @@
this.dontWarnPatterns = dontWarnPatterns;
// Translate the result of root-set computation into enqueuer actions.
enqueueRootItems(rootSet.noShrinking);
- TopDownClassHierarchyTraversal.forLibraryAndClasspathClasses(appView)
- // TODO(b/131813793): Would be beneficial to have `appView.appInfo().rootClasses()`.
- .visit(
- appView.appInfo().classes(), this::markAllLibraryAndClasspathVirtualMethodsReachable);
trace(executorService, timing);
options.reporter.failIfPendingErrors();
analyses.forEach(EnqueuerAnalysis::done);
@@ -2329,24 +2342,6 @@
}
}
- private void markAllLibraryAndClasspathVirtualMethodsReachable(DexClass clazz) {
- if (Log.ENABLED) {
- Log.verbose(
- getClass(), "Marking all methods of library class `%s` as reachable.", clazz.type);
- }
- // TODO(b/139464956, b/124480748): Remove this 'reason'. Lazy load libraries and no reporting.
- KeepReason reason = KeepReasonWitness.INSTANCE;
- for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
- markVirtualMethodAsReachable(
- encodedMethod.method,
- clazz.isInterface(),
- reason,
- this::shouldMarkLibraryMethodOverrideAsReachable,
- DexEncodedMethod::setLibraryMethodOverride,
- true);
- }
- }
-
private boolean shouldMarkLibraryMethodOverrideAsReachable(
DexProgramClass clazz, DexEncodedMethod method) {
assert method.isVirtualMethod();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
index ed45d7e..b1c93c1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
@@ -6,17 +6,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import com.android.tools.r8.BaseCommand;
import com.android.tools.r8.CompatProguardCommandBuilder;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.GenerateMainDexList;
import com.android.tools.r8.GenerateMainDexListCommand;
-import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
@@ -101,24 +98,13 @@
@Test
public void testR8() throws Exception {
- CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
- R8Command.Builder builder = R8Command.builder(diagnostics);
- buildInstrumentationTestCaseApplication(builder);
- R8Command command = builder
- .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .build();
- if (lookupLibraryBeforeProgram) {
- R8.run(command);
- diagnostics.assertEmpty();
- } else {
- try {
- R8.run(command);
- fail();
- } catch (CompilationFailedException e) {
- // Expected, as library class extending program class is an error for R8.
- }
- }
+ testForR8(Backend.DEX)
+ .apply(b -> buildInstrumentationTestCaseApplication(b.getBuilder()))
+ .setMinApi(AndroidApiLevel.B)
+ .addMainDexRules(keepInstrumentationTestCaseRules)
+ .compile()
+ // Library types and method overrides are lazily enqueued, thus no longer causing failures.
+ .assertNoMessages();
}
private static class CollectingDiagnosticHandler implements DiagnosticsHandler {