Add access control routines to resolution results.
Bug: 145187573
Change-Id: Ib3b83fae2bfd1a342ea544a7f0b58ffff1a132e5
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
new file mode 100644
index 0000000..3b4d52f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+/**
+ * Definitions of access control routines.
+ *
+ * <p>Follows SE 11, jvm spec, section 5.4.4 on "Access Control", except for aspects related to
+ * "run-time module", for which all items are assumed to be in the same single such module.
+ */
+public class AccessControl {
+
+ public static boolean isClassAccessible(DexClass clazz, DexProgramClass context) {
+ if (clazz.accessFlags.isPublic()) {
+ return true;
+ }
+ return clazz.getType().isSamePackage(context.getType());
+ }
+
+ public static boolean isMethodAccessible(
+ DexEncodedMethod method,
+ DexClass holder,
+ DexProgramClass context,
+ AppInfoWithSubtyping appInfo) {
+ return isMemberAccessible(method.accessFlags, holder, context, appInfo);
+ }
+
+ public static boolean isFieldAccessible(
+ DexEncodedField field,
+ DexClass holder,
+ DexProgramClass context,
+ AppInfoWithSubtyping appInfo) {
+ return isMemberAccessible(field.accessFlags, holder, context, appInfo);
+ }
+
+ private static boolean isMemberAccessible(
+ AccessFlags<?> memberFlags,
+ DexClass holder,
+ DexProgramClass context,
+ AppInfoWithSubtyping appInfo) {
+ if (!isClassAccessible(holder, context)) {
+ return false;
+ }
+ if (memberFlags.isPublic()) {
+ return true;
+ }
+ if (memberFlags.isPrivate()) {
+ return isNestMate(holder, context);
+ }
+ if (holder.getType().isSamePackage(context.getType())) {
+ return true;
+ }
+ if (!memberFlags.isProtected()) {
+ return false;
+ }
+ return appInfo.isSubtype(context.getType(), holder.getType());
+ }
+
+ private static boolean isNestMate(DexClass clazz, DexProgramClass context) {
+ if (clazz == context) {
+ return true;
+ }
+ if (!clazz.isInANest() || !context.isInANest()) {
+ return false;
+ }
+ return clazz.getNestHost() == context.getNestHost();
+ }
+}
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 270e91e..cf3b100 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.util.ArrayList;
@@ -19,6 +20,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
public class AppInfo implements DexDefinitionSupplier {
@@ -356,50 +358,53 @@
assert checkIfObsolete();
assert !clazz.isInterface();
// Step 2:
- DexEncodedMethod singleTarget = resolveMethodOnClassStep2(clazz, method);
- if (singleTarget != null) {
- return new SingleResolutionResult(singleTarget);
+ SingleResolutionResult result = resolveMethodOnClassStep2(clazz, method, clazz);
+ if (result != null) {
+ return result;
}
// Finally Step 3:
- return resolveMethodStep3(clazz, method);
+ return resolveMethodStep3(clazz, method, clazz);
}
/**
- * Implements step 2 of method resolution on classes as per
- * <a 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>.
+ * Implements step 2 of method resolution on classes as per <a
+ * 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 DexEncodedMethod resolveMethodOnClassStep2(DexClass clazz, DexMethod method) {
+ private SingleResolutionResult 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">
// Section 2.9 of the JVM Spec</a>.
DexEncodedMethod result = clazz.lookupSignaturePolymorphicMethod(method.name, dexItemFactory);
if (result != null) {
- return result;
+ return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
// Pt 2: Find a method that matches the descriptor.
result = clazz.lookupMethod(method);
if (result != null) {
- return result;
+ return new SingleResolutionResult(initialResolutionHolder, clazz, result);
}
// Pt 3: Apply step two to direct superclass of holder.
if (clazz.superType != null) {
DexClass superClass = definitionFor(clazz.superType);
if (superClass != null) {
- return resolveMethodOnClassStep2(superClass, method);
+ return resolveMethodOnClassStep2(superClass, method, initialResolutionHolder);
}
}
return null;
}
/**
- * Implements step 3 of
- * <a 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>. As this is the same for interfaces and classes, we share
- * one implementation.
+ * Implements step 3 of <a
+ * 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>. As this is the same for interfaces and classes, we share one
+ * implementation.
*/
- public ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
- MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+ public ResolutionResult resolveMethodStep3(
+ DexClass clazz, DexMethod method, DexClass initialResolutionHolder) {
+ MaximallySpecificMethodsBuilder builder =
+ new MaximallySpecificMethodsBuilder(initialResolutionHolder);
resolveMethodStep3Helper(clazz, method, builder);
return builder.resolve();
}
@@ -476,7 +481,7 @@
// Step 2: Look for exact method on interface.
DexEncodedMethod result = definition.lookupMethod(desc);
if (result != null) {
- return new SingleResolutionResult(result);
+ return new SingleResolutionResult(definition, definition, result);
}
// Step 3: Look for matching method on object class.
DexClass objectClass = definitionFor(dexItemFactory.objectType);
@@ -485,11 +490,11 @@
}
result = objectClass.lookupMethod(desc);
if (result != null && result.accessFlags.isPublic() && !result.accessFlags.isAbstract()) {
- return new SingleResolutionResult(result);
+ return new SingleResolutionResult(definition, objectClass, result);
}
// Step 3: Look for maximally-specific superinterface methods or any interface definition.
// This is the same for classes and interfaces.
- return resolveMethodStep3(definition, desc);
+ return resolveMethodStep3(definition, desc, definition);
}
/**
@@ -591,6 +596,8 @@
private static class MaximallySpecificMethodsBuilder {
+ private final DexClass initialResolutionHolder;
+
// The set of actual maximally specific methods.
// This set is linked map so that in the case where a number of methods remain a deterministic
// choice can be made. The map is from definition classes to their maximally specific method, or
@@ -599,6 +606,10 @@
// prior to writing.
LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
+ public MaximallySpecificMethodsBuilder(DexClass initialResolutionHolder) {
+ this.initialResolutionHolder = initialResolutionHolder;
+ }
+
void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
// If this candidate is already a candidate or it is shadowed, then no need to continue.
if (maximallySpecificMethods.containsKey(holder)) {
@@ -641,32 +652,42 @@
}
// Fast path in the common case of a single method.
if (maximallySpecificMethods.size() == 1) {
- return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
+ Entry<DexClass, DexEncodedMethod> first =
+ maximallySpecificMethods.entrySet().iterator().next();
+ return new SingleResolutionResult(
+ initialResolutionHolder, first.getKey(), first.getValue());
}
- DexEncodedMethod firstMaximallySpecificMethod = null;
- List<DexEncodedMethod> nonAbstractMethods = new ArrayList<>(maximallySpecificMethods.size());
- for (DexEncodedMethod method : maximallySpecificMethods.values()) {
+ Entry<DexClass, DexEncodedMethod> firstMaximallySpecificMethod = null;
+ List<Entry<DexClass, DexEncodedMethod>> nonAbstractMethods =
+ new ArrayList<>(maximallySpecificMethods.size());
+ for (Entry<DexClass, DexEncodedMethod> entry : maximallySpecificMethods.entrySet()) {
+ DexEncodedMethod method = entry.getValue();
if (method == null) {
// Ignore shadowed candidates.
continue;
}
if (firstMaximallySpecificMethod == null) {
- firstMaximallySpecificMethod = method;
+ firstMaximallySpecificMethod = entry;
}
if (method.isNonAbstractVirtualMethod()) {
- nonAbstractMethods.add(method);
+ nonAbstractMethods.add(entry);
}
}
// If there are no non-abstract methods, then any candidate will suffice as a target.
// For deterministic resolution, we return the first mapped method (of the linked map).
if (nonAbstractMethods.isEmpty()) {
- return new SingleResolutionResult(firstMaximallySpecificMethod);
+ return new SingleResolutionResult(
+ initialResolutionHolder,
+ firstMaximallySpecificMethod.getKey(),
+ firstMaximallySpecificMethod.getValue());
}
// If there is exactly one non-abstract method (a default method) it is the resolution target.
if (nonAbstractMethods.size() == 1) {
- return new SingleResolutionResult(nonAbstractMethods.get(0));
+ Entry<DexClass, DexEncodedMethod> entry = nonAbstractMethods.get(0);
+ return new SingleResolutionResult(
+ initialResolutionHolder, entry.getKey(), entry.getValue());
}
- return IncompatibleClassResult.create(nonAbstractMethods);
+ return IncompatibleClassResult.create(ListUtils.map(nonAbstractMethods, Entry::getValue));
}
}
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 1f8343e..16dff0d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -27,6 +27,11 @@
public abstract boolean hasSingleTarget();
+ public abstract boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo);
+
+ public abstract boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo);
+
public abstract boolean isValidVirtualTarget(InternalOptions options);
public abstract boolean isValidVirtualTargetForDynamicDispatch();
@@ -136,7 +141,9 @@
}
public static class SingleResolutionResult extends ResolutionResult {
- final DexEncodedMethod resolutionTarget;
+ final DexClass initialResolutionHolder;
+ final DexClass targetHolder;
+ final DexEncodedMethod targetMethod;
public static boolean isValidVirtualTarget(InternalOptions options, DexEncodedMethod target) {
return options.canUseNestBasedAccess()
@@ -144,24 +151,46 @@
: target.isVirtualMethod();
}
- public SingleResolutionResult(DexEncodedMethod resolutionTarget) {
- assert resolutionTarget != null;
- this.resolutionTarget = resolutionTarget;
+ public SingleResolutionResult(
+ DexClass initialResolutionHolder, DexClass targetHolder, DexEncodedMethod targetMethod) {
+ assert initialResolutionHolder != null;
+ assert targetHolder != null;
+ assert targetMethod != null;
+ assert targetHolder.type == targetMethod.method.holder;
+ this.targetHolder = targetHolder;
+ this.targetMethod = targetMethod;
+ this.initialResolutionHolder = initialResolutionHolder;
+ }
+
+ @Override
+ public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return AccessControl.isMethodAccessible(
+ targetMethod, initialResolutionHolder, context, appInfo);
+ }
+
+ @Override
+ public boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ // If a private method is accessible (which implies it is via its nest), then it is a valid
+ // virtual dispatch target if non-static.
+ return isAccessibleFrom(context, appInfo)
+ && (targetMethod.isVirtualMethod()
+ || (targetMethod.isPrivateMethod() && !targetMethod.isStatic()));
}
@Override
public boolean isValidVirtualTarget(InternalOptions options) {
- return isValidVirtualTarget(options, resolutionTarget);
+ return isValidVirtualTarget(options, targetMethod);
}
@Override
public boolean isValidVirtualTargetForDynamicDispatch() {
- return resolutionTarget.isVirtualMethod();
+ return targetMethod.isVirtualMethod();
}
@Override
public DexEncodedMethod getSingleTarget() {
- return resolutionTarget;
+ return targetMethod;
}
@Override
@@ -202,6 +231,17 @@
}
@Override
+ public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return true;
+ }
+
+ @Override
+ public boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return true;
+ }
+
+ @Override
public boolean isValidVirtualTarget(InternalOptions options) {
return true;
}
@@ -229,6 +269,17 @@
}
@Override
+ public boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return false;
+ }
+
+ @Override
+ public boolean isAccessibleForVirtualDispatchFrom(
+ DexProgramClass context, AppInfoWithSubtyping appInfo) {
+ return false;
+ }
+
+ @Override
public boolean isValidVirtualTarget(InternalOptions options) {
return false;
}
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 6881dbc..e559247 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
@@ -321,7 +321,11 @@
// If target is a non-interface library class it may be an emulated interface.
if (!libraryHolder.isInterface()) {
// Here we use step-3 of resolution to find a maximally specific default interface method.
- target = appView.appInfo().resolveMethodStep3(libraryHolder, method).getSingleTarget();
+ target =
+ appView
+ .appInfo()
+ .resolveMethodStep3(libraryHolder, method, libraryHolder)
+ .getSingleTarget();
if (target != null && rewriter.isEmulatedInterface(target.method.holder)) {
targetHolder = appView.definitionFor(target.method.holder);
addForward.accept(targetHolder, target);
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 708d328..e2641d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.ImmutableList;
@@ -1017,9 +1016,24 @@
DexType refinedReceiverType,
ClassTypeLatticeElement receiverLowerBoundType) {
assert checkIfObsolete();
- DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
- if (directResult != null) {
- return directResult;
+ // TODO: replace invocationContext by a DexProgramClass typed formal.
+ DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+ assert invocationClass != null;
+
+ ResolutionResult resolutionResult = resolveMethodOnClass(method.holder, method);
+ if (!resolutionResult.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
+ return null;
+ }
+
+ DexEncodedMethod topTarget = resolutionResult.getSingleTarget();
+ if (topTarget == null) {
+ // A null target represents a valid target without a known defintion, ie, array clone().
+ return null;
+ }
+
+ // If the target is a private method, then the invocation is a direct access to a nest member.
+ if (topTarget.isPrivateMethod()) {
+ return topTarget;
}
// If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
@@ -1027,7 +1041,6 @@
// from the runtime type of the receiver.
if (receiverLowerBoundType != null) {
if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
- ResolutionResult resolutionResult = resolveMethod(method.holder, method, false);
if (resolutionResult.hasSingleTarget()
&& resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
@@ -1049,14 +1062,13 @@
assert method != null;
assert isSubtype(refinedReceiverType, method.holder);
if (method.holder.isArrayType()) {
- // For javac output this will only be clone(), but in general the methods from Object can
- // be invoked with an array type holder.
return null;
}
DexClass holder = definitionFor(method.holder);
- if (holder == null || holder.isNotProgramClass() || holder.isInterface()) {
+ if (holder == null || holder.isNotProgramClass()) {
return null;
}
+ assert !holder.isInterface();
boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
DexProgramClass refinedHolder =
(refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder)
@@ -1098,21 +1110,6 @@
return result;
}
- private DexEncodedMethod nestAccessLookup(DexMethod method, DexType invocationContext) {
- if (method.holder == invocationContext || !definitionFor(invocationContext).isInANest()) {
- return null;
- }
- DexEncodedMethod directTarget = lookupDirectTarget(method);
- assert directTarget == null || directTarget.method.holder == method.holder;
- if (directTarget != null
- && directTarget.isPrivateMethod()
- && NestUtils.sameNest(method.holder, invocationContext, this)) {
- return directTarget;
- }
-
- return null;
- }
-
/**
* Computes which methods overriding <code>method</code> are visible for the subtypes of type.
*
@@ -1218,10 +1215,9 @@
DexType refinedReceiverType,
ClassTypeLatticeElement receiverLowerBoundType) {
assert checkIfObsolete();
- DexEncodedMethod directResult = nestAccessLookup(method, invocationContext);
- if (directResult != null) {
- return directResult;
- }
+ // Replace DexType invocationContext by DexProgramClass throughout.
+ DexProgramClass invocationClass = asProgramClassOrNull(definitionFor(invocationContext));
+ assert invocationClass != null;
// If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
// runtime type information. In this case, the invoke will dispatch to the resolution result
@@ -1249,13 +1245,24 @@
if (holder == null || !holder.accessFlags.isInterface()) {
return null;
}
- // First check that there is a target for this invoke-interface to hit. If there is none,
- // this will fail at runtime.
- DexEncodedMethod topTarget = resolveMethodOnInterface(holder, method).getSingleTarget();
- if (topTarget == null || !SingleResolutionResult.isValidVirtualTarget(options(), topTarget)) {
+ // First check that there is a visible and valid target for this invoke-interface to hit.
+ // If there is none, this will fail at runtime.
+ ResolutionResult topResolution = resolveMethodOnInterface(holder, method);
+ if (!topResolution.isAccessibleForVirtualDispatchFrom(invocationClass, this)) {
return null;
}
+ DexEncodedMethod topTarget = topResolution.getSingleTarget();
+ if (topTarget == null) {
+ // An null target represents a valid target with no known defintion, eg, array clone().
+ return null;
+ }
+
+ // If the target is a private method, then the invocation is a direct access to a nest member.
+ if (topTarget.isPrivateMethod()) {
+ return topTarget;
+ }
+
// If the invoke could target a method in a class that is not visible to R8, then give up.
if (canVirtualMethodBeImplementedInExtraSubclass(holder, method)) {
return null;
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 a9f60ec..a053f3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2022,7 +2022,7 @@
assert interfaceInvoke == holder.isInterface();
Set<DexEncodedMethod> possibleTargets =
// TODO(b/140214802): Call on the resolution once proper resolution and lookup is resolved.
- new SingleResolutionResult(resolution.method)
+ new SingleResolutionResult(holder, resolution.holder, resolution.method)
.lookupVirtualDispatchTargets(interfaceInvoke, appInfo);
if (possibleTargets == null || possibleTargets.isEmpty()) {
return;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 57eae6b..cefac4f 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
@@ -34,6 +35,7 @@
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
@@ -75,6 +77,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
@@ -588,6 +591,17 @@
return factory.createType(type.getDescriptor());
}
+ protected static DexField buildField(Field field, DexItemFactory factory) {
+ return buildField(Reference.fieldFromField(field), factory);
+ }
+
+ protected static DexField buildField(FieldReference field, DexItemFactory factory) {
+ return factory.createField(
+ buildType(field.getHolderClass(), factory),
+ buildType(field.getFieldType(), factory),
+ field.getFieldName());
+ }
+
protected static DexMethod buildMethod(Method method, DexItemFactory factory) {
return buildMethod(Reference.methodFromMethod(method), factory);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
index d448e1e..4b5e452 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestMethodInlinedTest.java
@@ -40,6 +40,15 @@
}
@Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramFiles(classesMatching("NestPvtMethodCallInlined"))
+ .run(parameters.getRuntime(), getMainClass("pvtCallInlined"))
+ .disassemble()
+ .assertSuccessWithOutput(getExpectedResult("pvtCallInlined"));
+ }
+
+ @Test
public void testPvtMethodCallInlined() throws Exception {
List<Path> toCompile = classesMatching("NestPvtMethodCallInlined");
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
index 63c6161..5c1d7a8 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestStaticMethodAccessTest.java
@@ -14,7 +14,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.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -71,10 +70,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
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 a487f40..bd6dbed 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
@@ -14,7 +14,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.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
index 59267b2..69cdad5 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestVirtualMethodAccessTest.java
@@ -14,7 +14,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.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -73,10 +72,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
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 68cc59e..d6343fc 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
@@ -17,7 +17,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.ir.optimize.NestUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.transformers.ClassFileTransformer;
import com.android.tools.r8.utils.BooleanUtils;
@@ -76,10 +75,7 @@
appInfo.definitionFor(buildType(B.class, appInfo.dexItemFactory())).asProgramClass();
DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
- // TODO(b/145187573): Update to check the full access control once possible.
- assertEquals(
- inSameNest,
- NestUtils.sameNest(bClass.type, resolutionResult.getSingleTarget().method.holder, appView));
+ assertEquals(inSameNest, resolutionResult.isAccessibleFrom(bClass, appInfo));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
new file mode 100644
index 0000000..a997622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/SelfVirtualMethodAccessTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.resolution.access;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+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.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SelfVirtualMethodAccessTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A::bar");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SelfVirtualMethodAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class);
+ }
+
+ public Collection<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ transformer(A.class).setPrivate(A.class.getDeclaredMethod("bar")).transform());
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
+ Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass aClass =
+ appInfo.definitionFor(buildType(A.class, appInfo.dexItemFactory())).asProgramClass();
+ DexMethod bar = buildMethod(A.class.getDeclaredMethod("bar"), appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+ assertTrue(resolutionResult.isAccessibleFrom(aClass, appInfo));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransformedClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(
+ result -> {
+ if (parameters.isCfRuntime()) {
+ result.assertSuccessWithOutput(EXPECTED);
+ } else {
+ // TODO(b/145187969): R8 compiles an incorrect program.
+ result.assertFailureWithErrorThatMatches(
+ containsString(NullPointerException.class.getName()));
+ }
+ });
+ }
+
+ static class A {
+ /* will be private */ void bar() {
+ System.out.println("A::bar");
+ }
+
+ public void foo() {
+ // Virtual invoke to private method.
+ bar();
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
new file mode 100644
index 0000000..e517531
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/IndirectFieldAccessTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.resolution.access.indirectfield;
+
+import static org.junit.Assert.assertTrue;
+
+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.graph.AccessControl;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.resolution.access.indirectfield.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectFieldAccessTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("42");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IndirectFieldAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public List<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class, A.class, B.class, C.class);
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(readClasses(getClasses()), Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass cClass =
+ appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+ DexField f =
+ buildField(
+ // Reflecting on B.class.getField("f") will give A.f, so manually create the reference.
+ Reference.field(Reference.classFromClass(B.class), "f", Reference.INT),
+ appInfo.dexItemFactory());
+ DexClass initialResolutionHolder = appInfo.definitionFor(f.holder);
+ DexEncodedField resolutionTarget = appInfo.resolveField(f);
+ // TODO(b/145723539): Test access via the resolution result once possible.
+ assertTrue(
+ AccessControl.isFieldAccessible(
+ resolutionTarget, initialResolutionHolder, cClass, appInfo));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .disassemble()
+ .apply(this::checkExpectedResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkExpectedResult);
+ }
+
+ private void checkExpectedResult(TestRunResult<?> result) {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+
+ /* non-public */ static class A {
+ public int f = 42;
+ }
+
+ public static class B extends A {
+ // Intentionally emtpy.
+ // Provides access to A.f from outside this package, eg, from pkg.C::bar().
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new C().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
new file mode 100644
index 0000000..9094062
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectfield/pkg/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.resolution.access.indirectfield.pkg;
+
+import com.android.tools.r8.resolution.access.indirectfield.IndirectFieldAccessTest.B;
+
+public class C {
+ public void bar() {
+ System.out.println(new B().f);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
new file mode 100644
index 0000000..1d75a8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/IndirectMethodAccessTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.resolution.access.indirectmethod;
+
+import static org.junit.Assert.assertTrue;
+
+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.graph.AppView;
+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.resolution.access.indirectmethod.pkg.C;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IndirectMethodAccessTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("A::foo");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public IndirectMethodAccessTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ public List<Class<?>> getClasses() {
+ return ImmutableList.of(Main.class, A.class, C.class);
+ }
+
+ private List<byte[]> getTransforms() throws IOException {
+ return ImmutableList.of(
+ // Compilation with javac will generate a synthetic bridge for foo. Remove it.
+ transformer(B.class)
+ .removeMethods((access, name, descriptor, signature, exceptions) -> name.equals("foo"))
+ .transform());
+ }
+
+ @Test
+ public void testResolutionAccess() throws Exception {
+ AppView<AppInfoWithLiveness> appView =
+ computeAppViewWithLiveness(
+ buildClasses(getClasses()).addClassProgramData(getTransforms()).build(), Main.class);
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexProgramClass cClass =
+ appInfo.definitionFor(buildType(C.class, appInfo.dexItemFactory())).asProgramClass();
+ DexMethod bar = buildMethod(B.class.getMethod("foo"), appInfo.dexItemFactory());
+ ResolutionResult resolutionResult = appInfo.resolveMethod(bar.holder, bar);
+ assertTrue(resolutionResult.isAccessibleForVirtualDispatchFrom(cClass, appInfo));
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransforms())
+ .run(parameters.getRuntime(), Main.class)
+ .disassemble()
+ .apply(this::checkExpectedResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(getClasses())
+ .addProgramClassFileData(getTransforms())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkExpectedResult);
+ }
+
+ private void checkExpectedResult(TestRunResult<?> result) {
+ result.assertSuccessWithOutput(EXPECTED);
+ }
+
+ /* non-public */ static class A {
+ public void foo() {
+ System.out.println("A::foo");
+ }
+ }
+
+ public static class B extends A {
+ // Intentionally emtpy.
+ // Provides access to A.foo from outside this package, eg, from pkg.C::bar().
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new C().bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
new file mode 100644
index 0000000..2ff8db8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/access/indirectmethod/pkg/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.resolution.access.indirectmethod.pkg;
+
+import com.android.tools.r8.resolution.access.indirectmethod.IndirectMethodAccessTest.B;
+
+public class C {
+ public void bar() {
+ new B().foo();
+ }
+}
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 09faf46..461323d 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -134,7 +134,7 @@
return this;
}
- /** Base addtion of a transformer on methods. */
+ /** Base addition of a transformer on methods. */
public ClassFileTransformer addMethodTransformer(MethodTransformer transformer) {
methodTransformers.add(transformer);
return this;
@@ -263,6 +263,24 @@
});
}
+ @FunctionalInterface
+ public interface MethodPredicate {
+ boolean test(int access, String name, String descriptor, String signature, String[] exceptions);
+ }
+
+ public ClassFileTransformer removeMethods(MethodPredicate predicate) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ return predicate.test(access, name, descriptor, signature, exceptions)
+ ? null
+ : super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+ });
+ }
+
/** Abstraction of the MethodVisitor.visitMethodInsn method with its continuation. */
@FunctionalInterface
public interface MethodInsnTransform {