Predicates for distinguishing IllegalAccessOrNoSuchMethodResult
Change-Id: I29cd20af61d5bfd4c80e52206ab65b713a886406
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 51ff1e2..cf2c789 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -25,15 +25,16 @@
public static OptionalBool isClassAccessible(
DexClass clazz,
- ProgramDefinition context,
+ Definition context,
ClassToFeatureSplitMap classToFeatureSplitMap,
SyntheticItems syntheticItems) {
if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
return OptionalBool.FALSE;
}
if (clazz.isProgramClass()
+ && context.isProgramDefinition()
&& !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
- clazz.asProgramClass(), context, syntheticItems)) {
+ clazz.asProgramClass(), context.asProgramDefinition(), syntheticItems)) {
return OptionalBool.UNKNOWN;
}
return OptionalBool.TRUE;
@@ -47,7 +48,7 @@
return isMemberAccessible(
resolutionResult.getResolutionPair(),
resolutionResult.getInitialResolutionHolder(),
- context,
+ context.getContextClass(),
appInfo);
}
@@ -56,13 +57,14 @@
DexClass initialResolutionHolder,
ProgramDefinition context,
AppView<? extends AppInfoWithClassHierarchy> appView) {
- return isMemberAccessible(member, initialResolutionHolder, context, appView.appInfo());
+ return isMemberAccessible(
+ member, initialResolutionHolder, context.getContextClass(), appView.appInfo());
}
- public static OptionalBool isMemberAccessible(
+ static OptionalBool isMemberAccessible(
DexClassAndMember<?, ?> member,
DexClass initialResolutionHolder,
- ProgramDefinition context,
+ DexClass context,
AppInfoWithClassHierarchy appInfo) {
AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
OptionalBool classAccessibility =
@@ -78,25 +80,28 @@
return classAccessibility;
}
if (memberFlags.isPrivate()) {
- if (!isNestMate(member.getHolder(), context.getContextClass())) {
+ if (!isNestMate(member.getHolder(), context)) {
return OptionalBool.FALSE;
}
return classAccessibility;
}
- if (member.getHolderType().isSamePackage(context.getContextType())) {
+ if (member.getHolderType().isSamePackage(context.getType())) {
return classAccessibility;
}
- if (memberFlags.isProtected()
- && appInfo.isSubtype(context.getContextType(), member.getHolderType())) {
+ if (memberFlags.isProtected() && appInfo.isSubtype(context.getType(), member.getHolderType())) {
return classAccessibility;
}
return OptionalBool.FALSE;
}
- private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
+ private static boolean isNestMate(DexClass clazz, DexClass context) {
if (clazz == context) {
return true;
}
+ if (context == null) {
+ assert false : "context should not be null";
+ return false;
+ }
if (!clazz.isInANest() || !context.isInANest()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 0056aff..2ee3013 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -711,7 +711,7 @@
// 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 IllegalAccessOrNoSuchMethodResult(initialResolutionHolder, result);
}
return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
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 d846788..e3558a2 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.InstantiatedObject;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.OptionalBool;
import java.util.ArrayList;
@@ -60,6 +61,14 @@
return false;
}
+ public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+ return false;
+ }
+
+ public boolean isIllegalAccessErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+ return false;
+ }
+
/** Returns non-null if isFailedResolution() is true, otherwise null. */
public FailedResolutionResult asFailedResolution() {
return null;
@@ -814,6 +823,10 @@
public boolean isVirtualTarget() {
return false;
}
+
+ public boolean hasMethodsCausingError() {
+ return false;
+ }
}
public static class ClassNotFoundResult extends FailedResolutionResult {
@@ -824,7 +837,7 @@
}
}
- abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
+ public abstract static class FailedResolutionWithCausingMethods extends FailedResolutionResult {
private final Collection<DexEncodedMethod> methodsCausingError;
@@ -836,6 +849,11 @@
public void forEachFailureDependency(Consumer<DexEncodedMethod> methodCausingFailureConsumer) {
this.methodsCausingError.forEach(methodCausingFailureConsumer);
}
+
+ @Override
+ public boolean hasMethodsCausingError() {
+ return methodsCausingError.size() > 0;
+ }
}
public static class IncompatibleClassResult extends FailedResolutionWithCausingMethods {
@@ -861,13 +879,70 @@
public static class NoSuchMethodResult extends FailedResolutionResult {
static final NoSuchMethodResult INSTANCE = new NoSuchMethodResult();
+
+ @Override
+ public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+ return true;
+ }
}
- public static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
+ static class IllegalAccessOrNoSuchMethodResult extends FailedResolutionWithCausingMethods {
- public IllegalAccessOrNoSuchMethodResult(DexEncodedMethod methodCausingError) {
- super(Collections.singletonList(methodCausingError));
+ private final DexClass initialResolutionHolder;
+
+ public IllegalAccessOrNoSuchMethodResult(
+ DexClass initialResolutionHolder, Collection<DexEncodedMethod> methodsCausingError) {
+ super(methodsCausingError);
+ this.initialResolutionHolder = initialResolutionHolder;
+ }
+
+ public IllegalAccessOrNoSuchMethodResult(
+ DexClass initialResolutionHolder, DexEncodedMethod methodCausingError) {
+ this(initialResolutionHolder, Collections.singletonList(methodCausingError));
assert methodCausingError != null;
}
+
+ @Override
+ public boolean isIllegalAccessErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+ if (!hasMethodsCausingError()) {
+ return false;
+ }
+ BooleanBox seenNoAccess = new BooleanBox(false);
+ forEachFailureDependency(
+ method -> {
+ DexClassAndMethod classAndMethod =
+ DexClassAndMethod.create(appInfo.definitionFor(method.getHolderType()), method);
+ seenNoAccess.or(
+ AccessControl.isMemberAccessible(
+ classAndMethod, initialResolutionHolder, context, appInfo)
+ .isFalse());
+ });
+ return seenNoAccess.get();
+ }
+
+ @Override
+ public boolean isNoSuchMethodErrorResult(DexClass context, AppInfoWithClassHierarchy appInfo) {
+ if (!hasMethodsCausingError()) {
+ return true;
+ }
+ if (isIllegalAccessErrorResult(context, appInfo)) {
+ return false;
+ }
+ // At this point we know we have methods causing errors but we have access to them. To be
+ // certain that this is the case where we have nest access but we are invoking a method with
+ // an incorrect symbolic reference, we directly test for it by having an assert.
+ assert verifyInvalidSymbolicReference();
+ return true;
+ }
+
+ private boolean verifyInvalidSymbolicReference() {
+ BooleanBox invalidSymbolicReference = new BooleanBox(true);
+ forEachFailureDependency(
+ method -> {
+ invalidSymbolicReference.and(
+ method.getHolderType() != initialResolutionHolder.getType());
+ });
+ return invalidSymbolicReference.get();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
index f6b790d..eff5807 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -45,4 +45,12 @@
public void unset() {
set(false);
}
+
+ public void and(boolean value) {
+ this.value = value && this.value;
+ }
+
+ public void or(boolean value) {
+ this.value = value || this.value;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 3bc2439..5a7423a 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -36,6 +36,13 @@
.isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
}
+ public boolean canUseDefaultAndStaticInterfaceMethodsWhenDesugaring() {
+ assert isCfRuntime() || isDexRuntime();
+ assert apiLevel != null;
+ return getApiLevel()
+ .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
+ }
+
// Convenience predicates.
public boolean isDexRuntime() {
return runtime.isDex();
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
index 6cfb572..b968127 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
@@ -33,7 +33,8 @@
@Parameterized.Parameters(name = "{0}, invalid:{1}")
public static List<Object[]> data() {
return buildParameters(
- getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values());
}
public DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest(
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
index 6321064..1684c5a 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
@@ -27,7 +27,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
public DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest(
@@ -64,8 +64,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8()
+ testForD8(parameters.getBackend())
.addProgramClasses(getProgramClasses())
.addProgramClassFileData(getProgramClassData())
.setMinApi(parameters.getApiLevel())
@@ -73,7 +72,7 @@
.run(parameters.getRuntime(), TestClass.class)
// TODO(b/182335909): Ideally, this should also throw ICCE when desugaring.
.applyIf(
- !parameters.canUseDefaultAndStaticInterfaceMethods()
+ !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
|| parameters.isDexRuntimeVersion(Version.V7_0_0),
r -> r.assertSuccessWithOutput(EXPECTED_INVALID),
r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java
index fcf3080..c442b75 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest.java
@@ -25,7 +25,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
public DefaultInterfaceMethodDesugaringWithPublicStaticResolutionTest(TestParameters parameters) {
@@ -61,8 +61,7 @@
@Test
public void testD8() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8()
+ testForD8(parameters.getBackend())
.addProgramClasses(getProgramClasses())
.addProgramClassFileData(getProgramClassData())
.setMinApi(parameters.getApiLevel())
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 75d4bbc..4d4419f 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodTest.java
@@ -4,17 +4,17 @@
package com.android.tools.r8.resolution;
import static com.android.tools.r8.ToolHelper.getMostRecentAndroidJar;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
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.DexClass;
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;
@@ -30,17 +30,24 @@
public class VirtualOverrideOfPrivateStaticMethodTest extends TestBase {
public interface I {
- default void f() {}
+ default void f() {
+ System.out.println("I::f");
+ }
}
public static class A {
- private static void f() {}
+ private static void f() {
+ System.out.println("A::f");
+ }
}
public static class B extends A implements I {}
public static class C extends B {
- public void f() {}
+ @Override
+ public void f() {
+ System.out.println("C::f");
+ }
}
public static class Main {
@@ -64,13 +71,13 @@
.appInfo();
}
- private static DexMethod buildMethod(Class clazz, String name) {
+ private static DexMethod buildMethod(Class<?> clazz, String name) {
return buildNullaryVoidMethod(clazz, name, appInfo.dexItemFactory());
}
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
private final TestParameters parameters;
@@ -85,30 +92,48 @@
@Test
public void resolveTarget() {
ResolutionResult resolutionResult = appInfo.resolveMethodOnClass(methodOnB, methodOnB.holder);
- assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+ DexClass context = appInfo.definitionFor(methodOnB.holder);
+ assertTrue(resolutionResult.isIllegalAccessErrorResult(context, appInfo));
}
@Test
- public void runTest() throws ExecutionException, CompilationFailedException, IOException {
- if (parameters.isCfRuntime()) {
- testForJvm()
- .addProgramClasses(CLASSES)
- .run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
- }
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(CLASSES)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(IllegalAccessError.class);
+ }
+
+ @Test
+ public void testD8() throws ExecutionException, CompilationFailedException, IOException {
+ testForD8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ // TODO(b/182335909): Ideally, this should IllegalAccessError.
+ .applyIf(
+ parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
+ && parameters.isCfRuntime(),
+ r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+ r -> r.assertSuccessWithOutputLines("C::f"));
+ }
+
+ @Test
+ public void testR8() throws ExecutionException, CompilationFailedException, IOException {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+ .assertFailureWithErrorThatThrows(expectedRuntimeError());
}
- private String expectedRuntimeError() {
+ private Class<? extends Throwable> expectedRuntimeError() {
if (parameters.isDexRuntime()
&& parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
- return "IncompatibleClassChangeError";
+ return IncompatibleClassChangeError.class;
}
- return "IllegalAccessError";
+ return IllegalAccessError.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 ce6f4d7..5498d06 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,7 +18,6 @@
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.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -124,7 +123,11 @@
// Resolution fails when there is a mismatch between the symbolic reference and the definition.
if (!symbolicReferenceIsDefiningType) {
- assertTrue(resolutionResult instanceof IllegalAccessOrNoSuchMethodResult);
+ if (inSameNest) {
+ assertTrue(resolutionResult.isNoSuchMethodErrorResult(callerClassDefinition, appInfo));
+ } else {
+ assertTrue(resolutionResult.isIllegalAccessErrorResult(callerClassDefinition, appInfo));
+ }
return;
}