Keep track of failed field and method resolutions
This error only materializes when we use repackaging on classes that
has invalid resolution. But we have a lot of those tests.
Bug: 172254047
Bug: 173191855
Change-Id: Ibebffff6a5279df40b5506d1a79a04cae43d4e55
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a1435fc..5e31835 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -813,7 +813,9 @@
lens, appBuilder.build(), memberRebindingLens.getPrevious());
}
}
- assert Repackaging.verifyIdentityRepackaging(appView);
+ if (appView.appInfo().hasLiveness()) {
+ assert Repackaging.verifyIdentityRepackaging(appView.withLiveness());
+ }
// Add automatic main dex classes to an eventual manual list of classes.
if (!options.mainDexKeepRules.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 2328408..ca9a57a 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -572,6 +572,14 @@
return builder.build();
}
+ public ImmutableSet<DexField> rewriteFields(Set<DexField> fields) {
+ ImmutableSet.Builder<DexField> builder = ImmutableSet.builder();
+ for (DexField field : fields) {
+ builder.add(getRenamedFieldSignature(field));
+ }
+ return builder.build();
+ }
+
public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) {
ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder();
map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value));
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index c1d5981..bfc685f 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
@@ -12,14 +13,14 @@
public abstract class TreeFixerBase {
- private final AppView<?> appView;
+ private final AppView<AppInfoWithLiveness> appView;
private final DexItemFactory dexItemFactory;
private final Map<DexType, DexProgramClass> programClassCache = new IdentityHashMap<>();
private final Map<DexType, DexProgramClass> synthesizedFromClasses = new IdentityHashMap<>();
private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
- public TreeFixerBase(AppView<?> appView) {
+ public TreeFixerBase(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
}
@@ -46,6 +47,32 @@
return to;
}
+ /** Rewrite missing references */
+ public void recordFailedResolutionChanges() {
+ // In order for optimizations to correctly rewrite field and method references that do not
+ // resolve, we create a mapping from each failed resolution target to its reference reference.
+ appView
+ .appInfo()
+ .getFailedFieldResolutionTargets()
+ .forEach(
+ field -> {
+ DexField fixedUpField = fixupFieldReference(field);
+ if (field != fixedUpField) {
+ recordFieldChange(field, fixedUpField);
+ }
+ });
+ appView
+ .appInfo()
+ .getFailedMethodResolutionTargets()
+ .forEach(
+ method -> {
+ DexMethod fixedUpMethod = fixupMethodReference(method);
+ if (method != fixedUpMethod) {
+ recordMethodChange(method, fixedUpMethod);
+ }
+ });
+ }
+
/** Callback to allow custom handling when an encoded method changes. */
public DexEncodedMethod recordMethodChange(DexEncodedMethod from, DexEncodedMethod to) {
recordMethodChange(from.method, to.method);
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 51e419b..eba0a87 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -73,8 +72,7 @@
return lens;
}
- public static boolean verifyIdentityRepackaging(
- AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public static boolean verifyIdentityRepackaging(AppView<AppInfoWithLiveness> appView) {
// Running the tree fixer with an identity mapping helps ensure that the fixup of of items is
// complete as the rewrite replaces all items regardless of repackaging.
// The identity mapping should result in no move callbacks being called.
@@ -140,10 +138,11 @@
return null;
}
RepackagingLens.Builder lensBuilder = new RepackagingLens.Builder();
+ RepackagingTreeFixer repackagingTreeFixer =
+ new RepackagingTreeFixer(appView, mappings, lensBuilder);
List<DexProgramClass> newProgramClasses =
new ArrayList<>(
- new RepackagingTreeFixer(appView, mappings, lensBuilder)
- .fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
+ repackagingTreeFixer.fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
appBuilder.replaceProgramClasses(newProgramClasses);
RepackagingLens lens = lensBuilder.build(appView);
new AnnotationFixer(lens).run(appBuilder.getProgramClasses());
@@ -156,10 +155,14 @@
private final Builder lensBuilder;
public RepackagingTreeFixer(
- AppView<?> appView, BiMap<DexType, DexType> mappings, Builder lensBuilder) {
+ AppView<AppInfoWithLiveness> appView,
+ BiMap<DexType, DexType> mappings,
+ Builder lensBuilder) {
super(appView);
+ assert mappings != null;
this.mappings = mappings;
this.lensBuilder = lensBuilder;
+ recordFailedResolutionChanges();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 744e0fc..363cb30 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -89,8 +89,11 @@
*/
private final Set<DexMethod> targetedMethods;
- /** Set of targets that lead to resolution errors, such as non-existing or invalid targets. */
- private final Set<DexMethod> failedResolutionTargets;
+ /** Method targets that lead to resolution errors such as non-existing or invalid targets. */
+ private final Set<DexMethod> failedMethodResolutionTargets;
+
+ /** Field targets that lead to resolution errors, such as non-existing or invalid targets. */
+ private final Set<DexField> failedFieldResolutionTargets;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -197,7 +200,8 @@
MissingClasses missingClasses,
Set<DexType> liveTypes,
Set<DexMethod> targetedMethods,
- Set<DexMethod> failedResolutionTargets,
+ Set<DexMethod> failedMethodResolutionTargets,
+ Set<DexField> failedFieldResolutionTargets,
Set<DexMethod> bootstrapMethods,
Set<DexMethod> methodsTargetedByInvokeDynamic,
Set<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -235,7 +239,8 @@
this.deadProtoTypes = deadProtoTypes;
this.liveTypes = liveTypes;
this.targetedMethods = targetedMethods;
- this.failedResolutionTargets = failedResolutionTargets;
+ this.failedMethodResolutionTargets = failedMethodResolutionTargets;
+ this.failedFieldResolutionTargets = failedFieldResolutionTargets;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -281,7 +286,8 @@
previous.getMissingClasses(),
CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedProgramTypes()),
previous.targetedMethods,
- previous.failedResolutionTargets,
+ previous.failedMethodResolutionTargets,
+ previous.failedFieldResolutionTargets,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
@@ -328,7 +334,8 @@
? Sets.difference(previous.liveTypes, prunedItems.getRemovedClasses())
: previous.liveTypes,
previous.targetedMethods,
- previous.failedResolutionTargets,
+ previous.failedMethodResolutionTargets,
+ previous.failedFieldResolutionTargets,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
@@ -420,7 +427,8 @@
this.deadProtoTypes = previous.deadProtoTypes;
this.liveTypes = previous.liveTypes;
this.targetedMethods = previous.targetedMethods;
- this.failedResolutionTargets = previous.failedResolutionTargets;
+ this.failedMethodResolutionTargets = previous.failedMethodResolutionTargets;
+ this.failedFieldResolutionTargets = previous.failedFieldResolutionTargets;
this.bootstrapMethods = previous.bootstrapMethods;
this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
@@ -533,11 +541,15 @@
}
public boolean isFailedResolutionTarget(DexMethod method) {
- return failedResolutionTargets.contains(method);
+ return failedMethodResolutionTargets.contains(method);
}
- public Set<DexMethod> getFailedResolutionTargets() {
- return failedResolutionTargets;
+ public Set<DexMethod> getFailedMethodResolutionTargets() {
+ return failedMethodResolutionTargets;
+ }
+
+ public Set<DexField> getFailedFieldResolutionTargets() {
+ return failedFieldResolutionTargets;
}
public boolean isBootstrapMethod(DexMethod method) {
@@ -997,7 +1009,8 @@
getMissingClasses().commitSyntheticItems(committedItems),
lens.rewriteTypes(liveTypes),
lens.rewriteMethods(targetedMethods),
- lens.rewriteMethods(failedResolutionTargets),
+ lens.rewriteMethods(failedMethodResolutionTargets),
+ lens.rewriteFields(failedFieldResolutionTargets),
lens.rewriteMethods(bootstrapMethods),
lens.rewriteMethods(methodsTargetedByInvokeDynamic),
lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
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 5ec2137..f7ac800 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -303,7 +303,10 @@
private final SetWithReason<DexEncodedMethod> targetedMethods;
/** Set of methods that have invalid resolutions or lookups. */
- private final Set<DexMethod> failedResolutionTargets;
+ private final Set<DexMethod> failedMethodResolutionTargets;
+
+ /** Set of methods that have invalid resolutions or lookups. */
+ private final Set<DexField> failedFieldResolutionTargets;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -427,7 +430,8 @@
// This set is only populated in edge cases due to multiple default interface methods.
// The set is generally expected to be empty and in the unlikely chance it is not, it will
// likely contain two methods. Thus the default capacity of 2.
- failedResolutionTargets = SetUtils.newIdentityHashSet(2);
+ failedMethodResolutionTargets = SetUtils.newIdentityHashSet(2);
+ failedFieldResolutionTargets = SetUtils.newIdentityHashSet(0);
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new LiveFieldsSet(graphReporter::registerField);
lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
@@ -1943,6 +1947,7 @@
FieldResolutionResult resolutionResult = appInfo.resolveField(field);
if (resolutionResult.isFailedOrUnknownResolution()) {
reportMissingField(field);
+ failedFieldResolutionTargets.add(field);
}
return resolutionResult;
}
@@ -1954,7 +1959,8 @@
ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
if (resolutionResult.isFailedResolution()) {
reportMissingMethod(method);
- markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), context, reason);
+ markFailedMethodResolutionTargets(
+ method, resolutionResult.asFailedResolution(), context, reason);
}
return resolutionResult.asSingleResolution();
}
@@ -1966,7 +1972,8 @@
ResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
if (resolutionResult.isFailedResolution()) {
reportMissingMethod(method);
- markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), context, reason);
+ markFailedMethodResolutionTargets(
+ method, resolutionResult.asFailedResolution(), context, reason);
}
return resolutionResult.asSingleResolution();
}
@@ -2104,6 +2111,7 @@
// TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
DexEncodedMethod encodedMethod = clazz.lookupMethod(reference);
if (encodedMethod == null) {
+ failedMethodResolutionTargets.add(reference);
reportMissingMethod(reference);
return;
}
@@ -2892,17 +2900,17 @@
}
}
- private void markFailedResolutionTargets(
+ private void markFailedMethodResolutionTargets(
DexMethod symbolicMethod,
FailedResolutionResult failedResolution,
ProgramDefinition context,
KeepReason reason) {
- failedResolutionTargets.add(symbolicMethod);
+ failedMethodResolutionTargets.add(symbolicMethod);
failedResolution.forEachFailureDependency(
method -> {
DexProgramClass clazz = getProgramClassOrNull(method.getHolderType(), context);
if (clazz != null) {
- failedResolutionTargets.add(method.method);
+ failedMethodResolutionTargets.add(method.method);
markMethodAsTargeted(new ProgramMethod(clazz, method), reason);
}
});
@@ -2948,7 +2956,7 @@
// If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
DexClassAndMethod target = resolution.lookupInvokeSuperTarget(from.getHolder(), appInfo);
if (target == null) {
- failedResolutionTargets.add(resolution.getResolvedMethod().method);
+ failedMethodResolutionTargets.add(resolution.getResolvedMethod().method);
return;
}
@@ -3461,7 +3469,8 @@
: missingClassesBuilder.ignoreMissingClasses(),
SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
Enqueuer.toDescriptorSet(targetedMethods.getItems()),
- Collections.unmodifiableSet(failedResolutionTargets),
+ Collections.unmodifiableSet(failedMethodResolutionTargets),
+ Collections.unmodifiableSet(failedFieldResolutionTargets),
Collections.unmodifiableSet(bootstrapMethods),
Collections.unmodifiableSet(methodsTargetedByInvokeDynamic),
Collections.unmodifiableSet(virtualMethodsTargetedByInvokeDirect),
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 e049766..c52fd0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -294,7 +294,7 @@
}
// The set of targets that must remain for proper resolution error cases should not be merged.
- for (DexMethod method : appInfo.getFailedResolutionTargets()) {
+ for (DexMethod method : appInfo.getFailedMethodResolutionTargets()) {
markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
}
}
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 5da5b2d..1d6dc16 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -44,6 +44,7 @@
public static class B extends A implements I {}
public static class C extends B {
+ @Override
public void f() {
System.out.println("Called C.f");
}