Resolution must fail if private resolutions don't match the symbolic reference.
This CL also shares the common parts of invoke-special and invoke-super. The
prevous implementation of invoke-super is invalid after the change to resolution
as it internally used resolution to lookup the target method.
Bug: 145775365
Bug: 145187969
Change-Id: I09b7dccced1c0e5e61b88d85f9bb428326034a9c
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index b906cb2..4a41c67 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -79,10 +79,16 @@
class UseCollector extends UseRegistry {
+ private DexProgramClass context;
+
UseCollector(DexItemFactory factory) {
super(factory);
}
+ public void setContext(DexProgramClass context) {
+ this.context = context;
+ }
+
@Override
public boolean registerInvokeVirtual(DexMethod method) {
DexEncodedMethod target = appInfo.lookupVirtualTarget(method.holder, method);
@@ -231,7 +237,10 @@
}
private void registerMethod(DexEncodedMethod method) {
- DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder);
+ DexEncodedMethod superTarget =
+ appInfo
+ .resolveMethod(method.method.holder, method.method)
+ .lookupInvokeSpecialTarget(context, appInfo);
if (superTarget != null) {
addMethod(superTarget.method);
}
@@ -344,6 +353,7 @@
private void analyze() {
UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
for (DexProgramClass dexProgramClass : application.classes()) {
+ useCollector.setContext(dexProgramClass);
useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType);
for (DexType implementsType : dexProgramClass.interfaces.values) {
useCollector.registerSuperType(dexProgramClass, implementsType);
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 cc8af52..b9542eb 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.ResolutionResult.ArrayCloneMethodResult;
import com.android.tools.r8.graph.ResolutionResult.ClassNotFoundResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
@@ -348,7 +349,7 @@
assert checkIfObsolete();
assert !clazz.isInterface();
// Step 2:
- SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+ ResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
if (result != null) {
return result;
}
@@ -361,7 +362,7 @@
* href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">Section
* 5.4.3.3 of the JVM Spec</a>.
*/
- private SingleResolutionResult resolveMethodOnClassStep2(
+ private ResolutionResult resolveMethodOnClassStep2(
DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
// Pt. 1: Signature polymorphic method check.
// See also <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9">
@@ -373,6 +374,14 @@
// Pt 2: Find a method that matches the descriptor.
result = clazz.lookupMethod(method);
if (result != null) {
+ // If the resolved method is private, then it can only be accessed if the symbolic reference
+ // that initiated the resolution was the type at which the method resolved on. If that is not
+ // the case, then the error is either an IllegalAccessError, or in the case where access is
+ // allowed because of nests, a NoSuchMethodError. Which error cannot be determined without
+ // knowing the calling context.
+ if (result.isPrivateMethod() && clazz != initialResolutionHolder) {
+ return new IllegalAccessOrNoSuchMethodResult(result);
+ }
return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
// Pt 3: Apply step two to direct superclass of holder.
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 65a2c7c..3080960 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -12,6 +12,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
+import java.util.function.BiPredicate;
import java.util.function.Consumer;
public abstract class ResolutionResult {
@@ -162,8 +163,70 @@
if (!isAccessibleFrom(context, appInfo)) {
return null;
}
+ DexEncodedMethod target =
+ internalInvokeSpecialOrSuper(
+ context, appInfo, (sup, sub) -> isSuperclass(sup, sub, appInfo));
+ if (target == null) {
+ return null;
+ }
+ // Should we check access control again?
+ DexClass holder = appInfo.definitionFor(target.method.holder);
+ if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
+ return null;
+ }
+ return target;
+ }
- // Statics cannot be targeted by invoke-special.
+ /**
+ * Lookup the target of an invoke-super.
+ *
+ * <p>This will return the target iff the resolution succeeded and the target is valid (i.e.,
+ * non-static and non-initializer) and accessible from {@code context}.
+ *
+ * <p>Additionally, this will also verify that the invoke-super is valid, i.e., it is on the a
+ * super type of the current context. Any invoke-special targeting the same type should have
+ * been mapped to an invoke-direct, but could change due to merging so we need to still allow
+ * the context to be equal to the targeted (symbolically referenced) type.
+ *
+ * @param context Class the invoke is contained in, i.e., the holder of the caller.
+ * @param appInfo Application info.
+ * @return The actual target for the invoke-super or {@code null} if no valid target is found.
+ */
+ @Override
+ public DexEncodedMethod lookupInvokeSuperTarget(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ if (!isAccessibleFrom(context, appInfo)) {
+ return null;
+ }
+ DexEncodedMethod target = lookupInvokeSuperTarget(context.asDexClass(), appInfo);
+ if (target == null) {
+ return null;
+ }
+ // Should we check access control again?
+ DexClass holder = appInfo.definitionFor(target.method.holder);
+ if (!AccessControl.isMethodAccessible(target, holder, context, appInfo)) {
+ return null;
+ }
+ return target;
+ }
+
+ @Override
+ public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
+ assert context != null;
+ if (resolvedMethod.isInstanceInitializer()
+ || (appInfo.hasSubtyping()
+ && initialResolutionHolder != context
+ && !isSuperclass(initialResolutionHolder, context, appInfo.withSubtyping()))) {
+ throw new CompilationError(
+ "Illegal invoke-super to " + resolvedMethod.toSourceString(), context.getOrigin());
+ }
+ return internalInvokeSpecialOrSuper(context, appInfo, (sup, sub) -> true);
+ }
+
+ private DexEncodedMethod internalInvokeSpecialOrSuper(
+ DexClass context, AppInfo appInfo, BiPredicate<DexClass, DexClass> isSuperclass) {
+
+ // Statics cannot be targeted by invoke-special/super.
if (getResolvedMethod().isStatic()) {
return null;
}
@@ -182,9 +245,9 @@
final DexClass initialType;
if (!resolvedMethod.isInstanceInitializer()
&& !symbolicReference.isInterface()
- && isSuperclass(symbolicReference, context, appInfo)) {
+ && isSuperclass.test(symbolicReference, context)) {
// If reference is a super type of the context then search starts at the immediate super.
- initialType = appInfo.definitionFor(context.superType);
+ initialType = context.superType == null ? null : appInfo.definitionFor(context.superType);
} else {
// Otherwise it starts at the reference itself.
initialType = symbolicReference;
@@ -228,10 +291,6 @@
if (target.isAbstract()) {
return null;
}
- // Should we check access control again?
- if (!AccessControl.isMethodAccessible(target, initialType, context, appInfo)) {
- return null;
- }
return target;
}
@@ -239,72 +298,6 @@
return sup != sub && appInfo.isSubtype(sub.type, sup.type);
}
- /**
- * Lookup super method following the super chain from the holder of {@code method}.
- *
- * <p>This method will resolve the method on the holder of {@code method} and only return a
- * non-null value if the result of resolution was an instance (i.e. non-static) method.
- *
- * <p>Additionally, this will also verify that the invoke super is valid, i.e., it is on the
- * same type or a super type of the current context. The spec says that it has invoke super
- * semantics, if the type is a supertype of the current class. If it is the same or a subtype,
- * it has invoke direct semantics. The latter case is illegal, so we map it to a super call
- * here. In R8, we abort at a later stage (see. See also <a href=
- * "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" </a>
- * for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/"
- * "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In
- * particular, the requirement isAssignable(class(CurrentClassName, L), class(MethodClassName,
- * L)). com.android.tools.r8.cf.code.CfInvoke#isInvokeSuper(DexType)}.
- *
- * @param context the class the invoke is contained in, i.e., the holder of the caller.
- * @param appInfo Application info.
- * @return The actual target for the invoke-super or {@code null} if none found.
- */
- @Override
- public DexEncodedMethod lookupInvokeSuperTarget(
- DexProgramClass context, AppInfoWithSubtyping appInfo) {
- if (!isAccessibleFrom(context, appInfo)) {
- return null;
- }
- return lookupInvokeSuperTarget(context.asDexClass(), appInfo);
- }
-
- @Override
- public DexEncodedMethod lookupInvokeSuperTarget(DexClass context, AppInfo appInfo) {
- assert context != null;
- DexMethod method = resolvedMethod.method;
- // TODO(b/145775365): Check the requirements for an invoke-special to a protected method.
- // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial
-
- if (appInfo.hasSubtyping()
- && !appInfo.withSubtyping().isSubtype(context.type, initialResolutionHolder.type)) {
- throw new CompilationError(
- "Illegal invoke-super to " + method.toSourceString() + " from class " + context,
- context.getOrigin());
- }
-
- // According to
- // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
- // the "symbolic reference" if the "symbolic reference" does not name a class.
- // TODO(b/145775365): This looks like the exact opposite of what the spec says, second item is
- // - is-class(sym-ref) => is-super(sym-ref, current-class)
- // this implication trivially holds for !is-class(sym-ref) == is-inteface(sym-ref), thus
- // the resolution should specifically *not* use the "symbolic reference".
- if (initialResolutionHolder.isInterface()) {
- // TODO(b/145775365): This does not consider a static method!
- return appInfo.resolveMethodOnInterface(initialResolutionHolder, method).getSingleTarget();
- }
- // Then, resume on the search, but this time, starting from the holder of the caller.
- if (context.superType == null) {
- return null;
- }
- SingleResolutionResult resolution =
- appInfo.resolveMethodOnClass(context.superType, method).asSingleResolution();
- return resolution != null && !resolution.resolvedMethod.isStatic()
- ? resolution.resolvedMethod
- : null;
- }
-
@Override
// TODO(b/140204899): Leverage refined receiver type if available.
public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
@@ -515,33 +508,45 @@
}
}
- public static class IncompatibleClassResult extends FailedResolutionResult {
- static final IncompatibleClassResult INSTANCE =
- new IncompatibleClassResult(Collections.emptyList());
+ abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
private final Collection<DexEncodedMethod> methodsCausingError;
- private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
+ private FailedResolutionWithCausingMethods(Collection<DexEncodedMethod> methodsCausingError) {
this.methodsCausingError = methodsCausingError;
}
- static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
- return methodsCausingError.isEmpty()
- ? INSTANCE
- : new IncompatibleClassResult(methodsCausingError);
- }
-
@Override
public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
}
- public static class NoSuchMethodResult extends FailedResolutionResult {
- static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+ public static class IncompatibleClassResult extends FailedResolutionWithCausingMethods {
+ static final IncompatibleClassResult INSTANCE =
+ new IncompatibleClassResult(Collections.emptyList());
- private NoSuchMethodResult() {
- // Intentionally left empty.
+ private IncompatibleClassResult(Collection<DexEncodedMethod> methodsCausingError) {
+ super(methodsCausingError);
+ }
+
+ static IncompatibleClassResult create(Collection<DexEncodedMethod> methodsCausingError) {
+ return methodsCausingError.isEmpty()
+ ? INSTANCE
+ : new IncompatibleClassResult(methodsCausingError);
+ }
+ }
+
+ public static class NoSuchMethodResult extends FailedResolutionResult {
+
+ static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+ }
+
+ public static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
+
+ public IllegalAccessOrNoSuchMethodResult(DexEncodedMethod methodCausingError) {
+ super(Collections.singletonList(methodCausingError));
+ assert methodCausingError != null;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index f4fbdde..dd95b28 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -290,8 +290,9 @@
ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
// If resolution fails, install a method throwing IncompatibleClassChangeError.
if (resolution.isFailedResolution()) {
- assert resolution instanceof IncompatibleClassResult;
- addICCEThrowingMethod(method, clazz);
+ if (resolution instanceof IncompatibleClassResult) {
+ addICCEThrowingMethod(method, clazz);
+ }
return;
}
DexEncodedMethod target = resolution.getSingleTarget();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
index f09f73f..5892466 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ArgumentRemovalUtils.java
@@ -15,7 +15,7 @@
public static boolean isPinned(DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
return appView.appInfo().isPinned(method.method)
|| appView.appInfo().bootstrapMethods.contains(method.method)
- || appView.appInfo().brokenSuperInvokes.contains(method.method)
+ || appView.appInfo().failedResolutionTargets.contains(method.method)
|| appView.appInfo().methodsTargetedByInvokeDynamic.contains(method.method)
|| method.accessFlags.isNative();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index a938ff2..194b84a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -227,9 +227,8 @@
for (int i = 0; i < directMethods.size(); i++) {
DexEncodedMethod method = directMethods.get(i);
- // If this is a private or static method that is targeted by an invoke-super instruction, then
- // don't remove any unused arguments.
- if (appView.appInfo().brokenSuperInvokes.contains(method.method)) {
+ // If this is a method with known resolution issues, then don't remove any unused arguments.
+ if (appView.appInfo().failedResolutionTargets.contains(method.method)) {
continue;
}
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 0aea415..25a7b15 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -82,7 +82,9 @@
*/
final SortedSet<DexMethod> targetedMethods;
- final Set<DexMethod> targetedMethodsThatMustRemainNonAbstract;
+ /** Set of targets that lead to resolution errors, such as non-existing or invalid targets. */
+ public final Set<DexMethod> failedResolutionTargets;
+
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
@@ -117,11 +119,6 @@
* will have been removed from the code.
*/
public final Set<DexCallSite> callSites;
- /**
- * Set of method signatures used in invoke-super instructions that either cannot be resolved or
- * resolve to a private method (leading to an IllegalAccessError).
- */
- public final SortedSet<DexMethod> brokenSuperInvokes;
/** Set of all items that have to be kept independent of whether they are used. */
final Set<DexReference> pinnedItems;
/** All items with assumemayhavesideeffects rule. */
@@ -189,7 +186,7 @@
Set<DexType> instantiatedAppServices,
Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
- Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
+ Set<DexMethod> failedResolutionTargets,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -201,7 +198,6 @@
SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes,
Set<DexCallSite> callSites,
- SortedSet<DexMethod> brokenSuperInvokes,
Set<DexReference> pinnedItems,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -228,7 +224,7 @@
this.instantiatedAppServices = instantiatedAppServices;
this.instantiatedTypes = instantiatedTypes;
this.targetedMethods = targetedMethods;
- this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
+ this.failedResolutionTargets = failedResolutionTargets;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -244,7 +240,6 @@
this.directInvokes = directInvokes;
this.staticInvokes = staticInvokes;
this.callSites = callSites;
- this.brokenSuperInvokes = brokenSuperInvokes;
this.alwaysInline = alwaysInline;
this.forceInline = forceInline;
this.neverInline = neverInline;
@@ -270,7 +265,7 @@
Set<DexType> instantiatedAppServices,
Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
- Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
+ Set<DexMethod> failedResolutionTargets,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -282,7 +277,6 @@
SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes,
Set<DexCallSite> callSites,
- SortedSet<DexMethod> brokenSuperInvokes,
Set<DexReference> pinnedItems,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
@@ -309,7 +303,7 @@
this.instantiatedAppServices = instantiatedAppServices;
this.instantiatedTypes = instantiatedTypes;
this.targetedMethods = targetedMethods;
- this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
+ this.failedResolutionTargets = failedResolutionTargets;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -325,7 +319,6 @@
this.directInvokes = directInvokes;
this.staticInvokes = staticInvokes;
this.callSites = callSites;
- this.brokenSuperInvokes = brokenSuperInvokes;
this.alwaysInline = alwaysInline;
this.forceInline = forceInline;
this.neverInline = neverInline;
@@ -352,7 +345,7 @@
previous.instantiatedAppServices,
previous.instantiatedTypes,
previous.targetedMethods,
- previous.targetedMethodsThatMustRemainNonAbstract,
+ previous.failedResolutionTargets,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
@@ -364,7 +357,6 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.brokenSuperInvokes,
previous.pinnedItems,
previous.mayHaveSideEffects,
previous.noSideEffects,
@@ -400,7 +392,7 @@
previous.instantiatedAppServices,
previous.instantiatedTypes,
previous.targetedMethods,
- previous.targetedMethodsThatMustRemainNonAbstract,
+ previous.failedResolutionTargets,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
@@ -412,7 +404,6 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.brokenSuperInvokes,
additionalPinnedItems == null
? previous.pinnedItems
: CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
@@ -452,8 +443,8 @@
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
- this.targetedMethodsThatMustRemainNonAbstract =
- lense.rewriteMethodsConservatively(previous.targetedMethodsThatMustRemainNonAbstract);
+ this.failedResolutionTargets =
+ lense.rewriteMethodsConservatively(previous.failedResolutionTargets);
this.bootstrapMethods = lense.rewriteMethodsConservatively(previous.bootstrapMethods);
this.methodsTargetedByInvokeDynamic =
lense.rewriteMethodsConservatively(previous.methodsTargetedByInvokeDynamic);
@@ -481,7 +472,6 @@
// TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
// after second tree shaking.
this.callSites = previous.callSites;
- this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
// Don't rewrite pruned types - the removed types are identified by their original name.
this.prunedTypes = previous.prunedTypes;
this.mayHaveSideEffects =
@@ -535,8 +525,7 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
- this.targetedMethodsThatMustRemainNonAbstract =
- previous.targetedMethodsThatMustRemainNonAbstract;
+ this.failedResolutionTargets = previous.failedResolutionTargets;
this.bootstrapMethods = previous.bootstrapMethods;
this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
@@ -552,7 +541,6 @@
this.directInvokes = previous.directInvokes;
this.staticInvokes = previous.staticInvokes;
this.callSites = previous.callSites;
- this.brokenSuperInvokes = previous.brokenSuperInvokes;
this.alwaysInline = previous.alwaysInline;
this.forceInline = previous.forceInline;
this.neverInline = previous.neverInline;
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 8329fcf..335f662 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -156,11 +156,6 @@
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
/**
- * Set of method signatures used in invoke-super instructions that either cannot be resolved or
- * resolve to a private method (leading to an IllegalAccessError).
- */
- private final Set<DexMethod> brokenSuperInvokes = Sets.newIdentityHashSet();
- /**
* This map keeps a view of all virtual methods that are reachable from virtual invokes. A method
* is reachable even if no live subtypes exist, so this is not sufficient for inclusion in the
* live set.
@@ -211,8 +206,8 @@
*/
private final SetWithReason<DexEncodedMethod> targetedMethods;
- /** Subset of 'targetedMethods' for which the method must not be marked abstract. */
- private final Set<DexEncodedMethod> targetedMethodsThatMustRemainNonAbstract;
+ /** Set of methods that have invalid resolutions or lookups. */
+ private final Set<DexMethod> failedResolutionTargets;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -329,7 +324,7 @@
// 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.
- targetedMethodsThatMustRemainNonAbstract = SetUtils.newIdentityHashSet(2);
+ failedResolutionTargets = SetUtils.newIdentityHashSet(2);
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new SetWithReason<>(graphReporter::registerField);
instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
@@ -1327,21 +1322,22 @@
annotation.annotation.collectIndexedItems(referenceMarker);
}
- private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
+ private ResolutionResult resolveMethod(DexMethod method, KeepReason reason) {
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
- if (resolutionResult == null) {
+ if (resolutionResult.isFailedResolution()) {
reportMissingMethod(method);
+ markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
+ }
+ return resolutionResult;
+ }
+
+ private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
+ SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
+ if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
return;
}
- DexEncodedMethod encodedMethod = resolutionResult.getSingleTarget();
- if (encodedMethod == null) {
- // Note: should this be reported too? Or is this unreachable?
- return;
- }
- DexProgramClass clazz = getProgramClassOrNull(encodedMethod.method.holder);
- if (clazz == null) {
- return;
- }
+ DexProgramClass clazz = resolution.getResolvedHolder().asProgramClass();
+ DexEncodedMethod encodedMethod = resolution.getResolvedMethod();
// We have to mark the resolved method as targeted even if it cannot actually be invoked
// to make sure the invocation will keep failing in the appropriate way.
@@ -1636,7 +1632,7 @@
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
+ // Note: it may be worthwhile to add a resolution cache here. If so, it must still ensure
// that all library override edges are reported to the kept-graph consumer.
ResolutionResult firstResolution =
appView.appInfo().resolveMethod(instantiatedClass, method.method);
@@ -2100,7 +2096,7 @@
appInfo.resolveMethod(method.holder, method, interfaceInvoke);
if (resolutionResult.isFailedResolution()) {
// If the resolution fails, mark each dependency causing a failure.
- markFailedResolutionTargets(resolutionResult.asFailedResolution(), reason);
+ markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), reason);
return MarkedResolutionTarget.unresolved();
}
@@ -2132,12 +2128,13 @@
}
private void markFailedResolutionTargets(
- FailedResolutionResult failedResolution, KeepReason reason) {
+ DexMethod symbolicMethod, FailedResolutionResult failedResolution, KeepReason reason) {
+ failedResolutionTargets.add(symbolicMethod);
failedResolution.forEachFailureDependency(
method -> {
DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
if (clazz != null) {
- targetedMethodsThatMustRemainNonAbstract.add(method);
+ failedResolutionTargets.add(method.method);
markMethodAsTargeted(clazz, method, reason);
}
});
@@ -2168,27 +2165,22 @@
// Package protected due to entry point from worklist.
void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
- // If the method does not resolve, mark it broken to avoid hiding errors in other optimizations.
- SingleResolutionResult resolution =
- appInfo.resolveMethod(method.holder, method).asSingleResolution();
+ KeepReason reason = KeepReason.targetedBySuperFrom(from);
+ SingleResolutionResult resolution = resolveMethod(method, reason).asSingleResolution();
if (resolution == null) {
- brokenSuperInvokes.add(method);
- reportMissingMethod(method);
return;
}
// If the resolution is in the program, mark it targeted.
if (resolution.getResolvedHolder().isProgramClass()) {
markMethodAsTargeted(
- resolution.getResolvedHolder().asProgramClass(),
- resolution.getResolvedMethod(),
- KeepReason.targetedBySuperFrom(from));
+ resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod(), reason);
}
// If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
// TODO(b/146016987): We should be passing the full program context and not looking it up again.
DexProgramClass fromHolder = appInfo.definitionFor(from.method.holder).asProgramClass();
DexEncodedMethod target = resolution.lookupInvokeSuperTarget(fromHolder, appInfo);
if (target == null) {
- brokenSuperInvokes.add(resolution.getResolvedMethod().method);
+ failedResolutionTargets.add(resolution.getResolvedMethod().method);
return;
}
@@ -2281,8 +2273,7 @@
Collections.unmodifiableSet(instantiatedAppServices),
SetUtils.mapIdentityHashSet(instantiatedTypes.getItems(), DexProgramClass::getType),
Enqueuer.toSortedDescriptorSet(targetedMethods.getItems()),
- SetUtils.mapIdentityHashSet(
- targetedMethodsThatMustRemainNonAbstract, DexEncodedMethod::getKey),
+ Collections.unmodifiableSet(failedResolutionTargets),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, bootstrapMethods),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, methodsTargetedByInvokeDynamic),
ImmutableSortedSet.copyOf(
@@ -2297,7 +2288,6 @@
toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
callSites,
- ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, brokenSuperInvokes),
pinnedItems,
rootSet.mayHaveSideEffects,
rootSet.noSideEffects,
@@ -2642,6 +2632,7 @@
markParameterAndReturnTypesAsLive(method);
}
+
private void markParameterAndReturnTypesAsLive(DexEncodedMethod method) {
for (DexType parameterType : method.method.proto.parameters.values) {
markTypeAsLive(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index e7a7fe5..116e315 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -297,7 +297,7 @@
&& !method.accessFlags.isSynchronized()
&& !method.accessFlags.isPrivate()
&& !method.accessFlags.isStatic()
- && !appInfo.targetedMethodsThatMustRemainNonAbstract.contains(method.method);
+ && !appInfo.failedResolutionTargets.contains(method.method);
// Private methods and static methods can only be targeted yet non-live as the result of
// an invalid invoke. They will not actually be called at runtime but we have to keep them
// as non-abstract (see above) to produce the same failure mode.
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 9a477cc..c518937 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -263,24 +263,6 @@
}
}
- // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
- // following example. (Alternatively, it would be possible to merge A and B and rewrite the
- // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This
- // situation should generally not occur in practice, though.)
- //
- // class A {}
- // class B extends A {
- // public void m() {}
- // }
- // class C extends A {
- // public void m() {
- // invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B
- // }
- // }
- for (DexMethod signature : appInfo.brokenSuperInvokes) {
- markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_SUPER);
- }
-
// It is valid to have an invoke-direct instruction in a default interface method that targets
// another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
// SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
@@ -290,7 +272,7 @@
}
// The set of targets that must remain for proper resolution error cases should not be merged.
- for (DexMethod method : appInfo.targetedMethodsThatMustRemainNonAbstract) {
+ for (DexMethod method : appInfo.failedResolutionTargets) {
markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
}
}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 0f613d5..cc8d46a 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -118,6 +118,7 @@
}
@Override
+ @Deprecated
public RR run(String mainClass)
throws CompilationFailedException, ExecutionException, IOException {
return compile().run(mainClass);
diff --git a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
index a346177..d7cdcd0 100644
--- a/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
+++ b/src/test/java/com/android/tools/r8/classFiltering/TestDesugar.java
@@ -9,8 +9,7 @@
lambda.consume("TestDesugar.consume");
}
-
- public interface Consumer {
+ interface Consumer {
public void consume(String s);
}
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 7fab7a1..cf013d1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -733,11 +733,6 @@
CF_DIR.resolve("SuperClassWithReferencedMethod.class"),
CF_DIR.resolve("SuperCallRewritingTest.class")
};
- Set<String> preservedClassNames =
- ImmutableSet.of(
- "classmerging.SubClassThatReferencesSuperMethod",
- "classmerging.SuperClassWithReferencedMethod",
- "classmerging.SuperCallRewritingTest");
// Build SubClassThatReferencesMethod.
SmaliBuilder smaliBuilder =
@@ -761,19 +756,28 @@
"move-result-object v1",
"return-object v1");
- // Build app.
- AndroidApp.Builder builder = AndroidApp.builder();
- builder.addProgramFiles(programFiles);
- builder.addDexProgramData(smaliBuilder.compile(), Origin.unknown());
+ String expectedOutput =
+ StringUtils.lines(
+ "Calling referencedMethod on SubClassThatReferencesSuperMethod",
+ "In referencedMethod on SubClassThatReferencesSuperMethod",
+ "In referencedMethod on SuperClassWithReferencedMethod",
+ "SuperClassWithReferencedMethod.referencedMethod()");
- // Run test.
- runTestOnInput(
- main,
- builder.build(),
- preservedClassNames::contains,
- // Prevent class merging, such that the generated code would be invalid if we rewrite the
- // invoke-super instruction into an invoke-direct instruction.
- getProguardConfig(EXAMPLE_KEEP, "-keep class *"));
+ testForD8()
+ .addProgramFiles(programFiles)
+ .addProgramDexFileData(smaliBuilder.compile())
+ .run(main)
+ .assertSuccessWithOutput(expectedOutput);
+
+ testForR8(Backend.DEX)
+ .addOptionsModification(this::configure)
+ .addKeepMainRule(main)
+ // Keep the classes to avoid merge, but don't keep methods which allows inlining.
+ .addKeepRules("-keep class *")
+ .addProgramFiles(programFiles)
+ .addProgramDexFileData(smaliBuilder.compile())
+ .run(main)
+ .assertSuccessWithOutput(expectedOutput);
}
// The following test checks that our rewriting of invoke-super instructions in a class F works
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index db68c25..289a0ba 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -170,7 +170,8 @@
assertNull(appInfo.lookupDirectTarget(methodXOnTest));
assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
- assertNotNull(appInfo.lookupStaticTarget(methodXOnTest));
+ // Accessing a private target on a different type will fail resolution outright.
+ assertNull(appInfo.lookupStaticTarget(methodXOnTest));
assertEquals("OK", runArt(application));
}
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 5fe17d5..eb4cd97 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -4,23 +4,21 @@
package com.android.tools.r8.resolution;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
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.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -81,22 +79,9 @@
}
@Test
- public void lookupSingleTarget() {
+ public void resolveTarget() {
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
- DexEncodedMethod resolved = resolutionResult.getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- DexEncodedMethod singleVirtualTarget =
- appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
- Assert.assertNull(singleVirtualTarget);
- }
-
- @Test
- public void lookupVirtualTargets() {
- ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- DexEncodedMethod resolved = resolutionResult.getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+ assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
}
@Test
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 22445e6..85ace0b 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -4,34 +4,29 @@
package com.android.tools.r8.resolution;
import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
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.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableList;
import java.util.List;
-import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends AsmTestBase {
+public class VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest extends TestBase {
public interface I {
default void f() {}
@@ -64,51 +59,26 @@
public static class BaseDump implements Opcodes {
- static String prefix(String suffix) {
- return VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.class
- .getTypeName()
- .replace('.', '/')
- + suffix;
- }
-
- public static byte[] dump() {
- ClassWriter cw = new ClassWriter(0);
- MethodVisitor mv;
- cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, prefix("$Base"), null, "java/lang/Object", null);
- cw.visitSource("VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java", null);
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- // Changed ACC_PRIVATE to ACC_PUBLIC
- mv = cw.visitMethod(ACC_PUBLIC, "f", "()V", null, null);
- mv.visitCode();
- mv.visitInsn(RETURN);
- mv.visitMaxs(0, 1);
- mv.visitEnd();
- }
- cw.visitEnd();
- return cw.toByteArray();
+ public static byte[] dump() throws Exception {
+ return transformer(Base.class).setPublic(Base.class.getDeclaredMethod("f")).transform();
}
}
public static List<Class<?>> CLASSES =
ImmutableList.of(A.class, B.class, C.class, I.class, Main.class);
- public static List<byte[]> DUMPS = ImmutableList.of(BaseDump.dump());
+ public static List<byte[]> getDumps() throws Exception {
+ return ImmutableList.of(BaseDump.dump());
+ }
private static AppInfoWithLiveness appInfo;
@BeforeClass
public static void computeAppInfo() throws Exception {
appInfo =
- computeAppViewWithLiveness(readClassesAndAsmDump(CLASSES, DUMPS), Main.class).appInfo();
+ computeAppViewWithLiveness(
+ buildClasses(CLASSES).addClassProgramData(getDumps()).build(), Main.class)
+ .appInfo();
}
private static DexMethod buildMethod(Class clazz, String name) {
@@ -130,21 +100,9 @@
}
@Test
- public void lookupSingleTarget() {
- DexEncodedMethod resolved =
- appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB).getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- DexEncodedMethod singleVirtualTarget =
- appInfo.lookupSingleVirtualTarget(methodOnB, methodOnB.holder);
- Assert.assertNull(singleVirtualTarget);
- }
-
- @Test
- public void lookupVirtualTargets() {
+ public void testResolution() {
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB.holder, methodOnB);
- DexEncodedMethod resolved = resolutionResult.getSingleTarget();
- assertEquals(methodOnA, resolved.method);
- assertFalse(resolutionResult.isValidVirtualTarget(appInfo.app().options));
+ assertTrue(resolutionResult.isFailedResolution());
}
@Test
@@ -154,13 +112,13 @@
runResult =
testForJvm()
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DUMPS)
+ .addProgramClassFileData(getDumps())
.run(parameters.getRuntime(), Main.class);
} else {
runResult =
testForD8()
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DUMPS)
+ .addProgramClassFileData(getDumps())
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class);
}
@@ -172,7 +130,7 @@
R8TestRunResult runResult =
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
- .addProgramClassFileData(DUMPS)
+ .addProgramClassFileData(getDumps())
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index 52dbd4b..8e21be0 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
@@ -121,6 +122,12 @@
assertEquals(method.holder, declaredClassDefinition.type);
ResolutionResult resolutionResult = appInfo.resolveMethod(declaredClassDefinition, method);
+ // Resolution fails when there is a mismatch between the symbolic reference and the definition.
+ if (!symbolicReferenceIsDefiningType) {
+ assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+ return;
+ }
+
// Verify that the resolved method is on the defining class.
assertEquals(
definingClassDefinition, resolutionResult.asSingleResolution().getResolvedHolder());
@@ -178,7 +185,7 @@
.addProgramClasses(getClasses())
.addProgramClassFileData(getTransformedClasses())
.run(parameters.getRuntime(), Main.class)
- .apply(result -> checkExpectedResult(result, false));
+ .apply(this::checkExpectedResult);
}
@Test
@@ -189,10 +196,10 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(result -> checkExpectedResult(result, true));
+ .apply(this::checkExpectedResult);
}
- private void checkExpectedResult(TestRunResult<?> result, boolean isR8) {
+ private void checkExpectedResult(TestRunResult<?> result) {
// If not in the same nest, the error is always illegal access.
if (!inSameNest) {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
@@ -201,11 +208,6 @@
// If in the same nest but the reference is not exact, the error is always no such method.
if (!symbolicReferenceIsDefiningType) {
- // TODO(b/145775365): R8 incorrectly compiles the input to a working program.
- if (isR8) {
- result.assertSuccessWithOutput(EXPECTED);
- return;
- }
// TODO(b/145775365): D8/R8 does not preserve the thrown error.
if (parameters.isDexRuntime()) {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
index bd6dbed..e3ca436 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessWithIntermediateClassTest.java
@@ -92,20 +92,11 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(
- result -> {
- if (inSameNest) {
- // TODO(b/145187969): R8 incorrectly compiles out the incorrect access.
- result.assertSuccessWithOutput(EXPECTED);
- } else {
- checkExpectedResult(result);
- }
- });
+ .apply(this::checkExpectedResult);
}
private void checkExpectedResult(TestRunResult<?> result) {
if (inSameNest && parameters.isCfRuntime()) {
- // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
} else {
result.assertFailureWithErrorThatMatches(containsString(IllegalAccessError.class.getName()));
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
index 3e85f1a..cdf2b22 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessWithIntermediateClassTest.java
@@ -91,23 +91,11 @@
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(Main.class)
.run(parameters.getRuntime(), Main.class)
- .apply(
- result -> {
- if (inSameNest && parameters.isCfRuntime()) {
- // TODO(b/145187969): R8/CF compiles to a "working" program.
- result.assertSuccessWithOutput(EXPECTED);
- } else if (inSameNest && parameters.isDexRuntime()) {
- // TODO(b/145187969): R8/DEX compiles to a throw null program.
- result.assertFailureWithErrorThatThrows(NullPointerException.class);
- } else {
- checkExpectedResult(result);
- }
- });
+ .apply(this::checkExpectedResult);
}
private void checkExpectedResult(TestRunResult<?> result) {
if (inSameNest && parameters.isCfRuntime()) {
- // TODO(b/145187969): Investigate if the change to NoSuchMethodError is according to spec?
result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
} else {
result.assertFailureWithErrorThatThrows(IllegalAccessError.class);
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 461323d..3d653d0 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -238,7 +239,27 @@
});
}
+ public ClassFileTransformer setPublic(Method method) {
+ return setAccessFlags(
+ method,
+ accessFlags -> {
+ accessFlags.unsetPrivate();
+ accessFlags.unsetProtected();
+ accessFlags.setPublic();
+ });
+ }
+
public ClassFileTransformer setPrivate(Method method) {
+ return setAccessFlags(
+ method,
+ accessFlags -> {
+ accessFlags.unsetPublic();
+ accessFlags.unsetProtected();
+ accessFlags.setPrivate();
+ });
+ }
+
+ public ClassFileTransformer setAccessFlags(Method method, Consumer<MethodAccessFlags> setter) {
return addClassTransformer(
new ClassTransformer() {
final MethodReference methodReference = Reference.methodFromMethod(method);
@@ -253,9 +274,7 @@
MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
if (name.equals(methodReference.getMethodName())
&& descriptor.equals(methodReference.getMethodDescriptor())) {
- accessFlags.unsetPublic();
- accessFlags.unsetProtected();
- accessFlags.setPrivate();
+ setter.accept(accessFlags);
}
return super.visitMethod(
accessFlags.getAsCfAccessFlags(), name, descriptor, signature, exceptions);