< prev index next >
test/jdk/java/lang/invoke/AccessControlTest.java
Print this page
@@ -1,7 +1,7 @@
/*
- * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
@@ -31,11 +31,10 @@
import java.lang.invoke.*;
import java.lang.reflect.*;
import java.lang.reflect.Modifier;
import java.util.*;
-import org.testng.*;
import org.testng.annotations.*;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodHandles.Lookup.*;
import static java.lang.invoke.MethodType.*;
@@ -60,50 +59,68 @@
}
private class LookupCase implements Comparable<LookupCase> {
final Lookup lookup;
final Class<?> lookupClass;
+ final Class<?> prevLookupClass;
final int lookupModes;
public LookupCase(Lookup lookup) {
this.lookup = lookup;
this.lookupClass = lookup.lookupClass();
+ this.prevLookupClass = lookup.previousLookupClass();
this.lookupModes = lookup.lookupModes();
+
assert(lookupString().equals(lookup.toString()));
numberOf(lookupClass().getClassLoader()); // assign CL#
}
- public LookupCase(Class<?> lookupClass, int lookupModes) {
+ public LookupCase(Class<?> lookupClass, Class<?> prevLookupClass, int lookupModes) {
this.lookup = null;
this.lookupClass = lookupClass;
+ this.prevLookupClass = prevLookupClass;
this.lookupModes = lookupModes;
numberOf(lookupClass().getClassLoader()); // assign CL#
}
public final Class<?> lookupClass() { return lookupClass; }
+ public final Class<?> prevLookupClass() { return prevLookupClass; }
public final int lookupModes() { return lookupModes; }
public Lookup lookup() { lookup.getClass(); return lookup; }
@Override
public int compareTo(LookupCase that) {
Class<?> c1 = this.lookupClass();
Class<?> c2 = that.lookupClass();
+ Class<?> p1 = this.prevLookupClass();
+ Class<?> p2 = that.prevLookupClass();
if (c1 != c2) {
int cmp = c1.getName().compareTo(c2.getName());
if (cmp != 0) return cmp;
cmp = numberOf(c1.getClassLoader()) - numberOf(c2.getClassLoader());
assert(cmp != 0);
return cmp;
+ } else if (p1 != p2){
+ if (p1 == null)
+ return 1;
+ else if (p2 == null)
+ return -1;
+ int cmp = p1.getName().compareTo(p2.getName());
+ if (cmp != 0) return cmp;
+ cmp = numberOf(p1.getClassLoader()) - numberOf(p2.getClassLoader());
+ assert(cmp != 0);
+ return cmp;
}
return -(this.lookupModes() - that.lookupModes());
}
@Override
public boolean equals(Object that) {
return (that instanceof LookupCase && equals((LookupCase)that));
}
public boolean equals(LookupCase that) {
return (this.lookupClass() == that.lookupClass() &&
+ this.prevLookupClass() == that.prevLookupClass() &&
this.lookupModes() == that.lookupModes());
}
@Override
public int hashCode() {
@@ -111,24 +128,29 @@
}
/** Simulate all assertions in the spec. for Lookup.toString. */
private String lookupString() {
String name = lookupClass.getName();
+ if (prevLookupClass != null)
+ name += "/" + prevLookupClass.getName();
String suffix = "";
if (lookupModes == 0)
suffix = "/noaccess";
else if (lookupModes == PUBLIC)
suffix = "/public";
- else if (lookupModes == (PUBLIC|UNCONDITIONAL))
+ else if (lookupModes == UNCONDITIONAL)
suffix = "/publicLookup";
else if (lookupModes == (PUBLIC|MODULE))
suffix = "/module";
- else if (lookupModes == (PUBLIC|MODULE|PACKAGE))
+ else if (lookupModes == (PUBLIC|PACKAGE)
+ || lookupModes == (PUBLIC|MODULE|PACKAGE))
suffix = "/package";
- else if (lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE))
+ else if (lookupModes == (PUBLIC|PACKAGE|PRIVATE)
+ || lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE))
suffix = "/private";
- else if (lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED))
+ else if (lookupModes == (PUBLIC|PACKAGE|PRIVATE|PROTECTED)
+ || lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED))
suffix = "";
else
suffix = "/#"+Integer.toHexString(lookupModes);
return name+suffix;
}
@@ -136,86 +158,140 @@
/** Simulate all assertions from the spec. for Lookup.in:
* <hr>
* Creates a lookup on the specified new lookup class.
* [A1] The resulting object will report the specified
* class as its own {@link #lookupClass lookupClass}.
- * <p>
* [A2] However, the resulting {@code Lookup} object is guaranteed
* to have no more access capabilities than the original.
* In particular, access capabilities can be lost as follows:<ul>
- * <li> [A3] If the old lookup class is in a named module, and the new
- * lookup class is in a different module {@code M}, then no members, not
- * even public members in {@code M}'s exported packages, will be accessible.
- * The exception to this is when this lookup is publicLookup, in which case
- * public access is not lost.
- * <li> [A4] If the old lookup class is in an unnamed module, and the new
- * lookup class is a different module then module access is lost.
- * <li> [A5] If the new lookup class differs from the old one then UNCONDITIONAL
- * is lost. If the new lookup class is not within the same package member as the
- * old one, protected members will not be accessible by virtue of inheritance.
+ * [A3] If the new lookup class is in a different module from the old one,
+ * i.e. {@link #MODULE MODULE} access is lost.
+ * [A4] If the new lookup class is in a different package
+ * than the old one, protected and default (package) members will not be accessible,
+ * i.e. {@link #PROTECTED PROTECTED} and {@link #PACKAGE PACKAGE} access are lost.
+ * [A5] If the new lookup class is not within the same package member
+ * as the old one, private members will not be accessible, and protected members
+ * will not be accessible by virtue of inheritance,
+ * i.e. {@link #PRIVATE PRIVATE} access is lost.
* (Protected members may continue to be accessible because of package sharing.)
- * <li> [A6] If the new lookup class is in a different package than the old one,
- * protected and default (package) members will not be accessible.
- * <li> [A7] If the new lookup class is not within the same package member
- * as the old one, private members will not be accessible.
- * <li> [A8] If the new lookup class is not accessible to the old lookup class,
- * then no members, not even public members, will be accessible.
- * <li> [A9] (In all other cases, public members will continue to be accessible.)
- * </ul>
+ * [A6] If the new lookup class is not
+ * {@linkplain #accessClass(Class) accessible} to this lookup,
+ * then no members, not even public members, will be accessible
+ * i.e. all access modes are lost.
+ * [A7] If the new lookup class, the old lookup class and the previous lookup class
+ * are all in different modules i.e. teleporting to a third module,
+ * all access modes are lost.
+ * <p>
+ * The new previous lookup class is chosen as follows:
+ * [A8] If the new lookup object has {@link #UNCONDITIONAL UNCONDITIONAL} bit,
+ * the new previous lookup class is {@code null}.
+ * [A9] If the new lookup class is in the same module as the old lookup class,
+ * the new previous lookup class is the old previous lookup class.
+ * [A10] If the new lookup class is in a different module from the old lookup class,
+ * the new previous lookup class is the the old lookup class.
+ *
* Other than the above cases, the new lookup will have the same
- * access capabilities as the original. [A10]
+ * access capabilities as the original. [A11]
* <hr>
*/
public LookupCase in(Class<?> c2) {
Class<?> c1 = lookupClass();
- int m1 = lookupModes();
+ Module m1 = c1.getModule();
+ Module m2 = c2.getModule();
+ Module m0 = prevLookupClass() != null ? prevLookupClass.getModule() : c1.getModule();
+ int modes1 = lookupModes();
int changed = 0;
// for the purposes of access control then treat classes in different unnamed
// modules as being in the same module.
- boolean sameModule = (c1.getModule() == c2.getModule()) ||
- (!c1.getModule().isNamed() && !c2.getModule().isNamed());
+ boolean sameModule = (m1 == m2) ||
+ (!m1.isNamed() && !m2.isNamed());
boolean samePackage = (c1.getClassLoader() == c2.getClassLoader() &&
c1.getPackageName().equals(c2.getPackageName()));
boolean sameTopLevel = (topLevelClass(c1) == topLevelClass(c2));
boolean sameClass = (c1 == c2);
assert(samePackage || !sameTopLevel);
assert(sameTopLevel || !sameClass);
boolean accessible = sameClass;
- if ((m1 & PACKAGE) != 0) accessible |= samePackage;
- if ((m1 & PUBLIC ) != 0) accessible |= (c2.getModifiers() & PUBLIC) != 0;
- if (!sameModule) {
- if (c1.getModule().isNamed() && (m1 & UNCONDITIONAL) == 0) {
- accessible = false; // [A3]
- } else {
- changed |= (MODULE|PACKAGE|PRIVATE|PROTECTED); // [A3] [A4]
+
+ if ((modes1 & PACKAGE) != 0) accessible |= samePackage;
+ if ((modes1 & PUBLIC ) != 0) {
+ if (isModuleAccessible(c2))
+ accessible |= (c2.getModifiers() & PUBLIC) != 0;
+ else
+ accessible = false;
}
+ if ((modes1 & UNCONDITIONAL) != 0) {
+ if (m2.isExported(c2.getPackageName()))
+ accessible |= (c2.getModifiers() & PUBLIC) != 0;
+ else
+ accessible = false;
}
if (!accessible) {
- // Different package and no access to c2; lose all access.
- changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED); // [A8]
+ // no access to c2; lose all access.
+ changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED|UNCONDITIONAL); // [A6]
+ }
+ if (m2 != m1 && m0 != m1) {
+ // hop to a third module; lose all access
+ changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED); // [A7]
+ }
+ if (!sameModule) {
+ changed |= MODULE; // [A3]
}
if (!samePackage) {
// Different package; loose PACKAGE and lower access.
- changed |= (PACKAGE|PRIVATE|PROTECTED); // [A6]
+ changed |= (PACKAGE|PRIVATE|PROTECTED); // [A4]
}
if (!sameTopLevel) {
// Different top-level class. Lose PRIVATE and PROTECTED access.
- changed |= (PRIVATE|PROTECTED); // [A5] [A7]
+ changed |= (PRIVATE|PROTECTED); // [A5]
}
- if (!sameClass) {
- changed |= (UNCONDITIONAL); // [A5]
- } else {
- assert(changed == 0); // [A10] (no deprivation if same class)
+ if (sameClass) {
+ assert(changed == 0); // [A11] (no deprivation if same class)
}
- if (accessible) assert((changed & PUBLIC) == 0); // [A9]
- int m2 = m1 & ~changed;
- LookupCase l2 = new LookupCase(c2, m2);
+
+ if (accessible) assert((changed & PUBLIC) == 0);
+ int modes2 = modes1 & ~changed;
+ Class<?> plc = (m1 == m2) ? prevLookupClass() : c1; // [A9] [A10]
+ if ((modes1 & UNCONDITIONAL) != 0) plc = null; // [A8]
+ LookupCase l2 = new LookupCase(c2, plc, modes2);
assert(l2.lookupClass() == c2); // [A1]
- assert((m1 | m2) == m1); // [A2] (no elevation of access)
+ assert((modes1 | modes2) == modes1); // [A2] (no elevation of access)
+ assert(l2.prevLookupClass() == null || (modes2 & MODULE) == 0);
return l2;
}
+ LookupCase dropLookupMode(int modeToDrop) {
+ int oldModes = lookupModes();
+ int newModes = oldModes & ~(modeToDrop | PROTECTED);
+ switch (modeToDrop) {
+ case PUBLIC: newModes &= ~(MODULE|PACKAGE|PROTECTED|PRIVATE); break;
+ case MODULE: newModes &= ~(PACKAGE|PRIVATE); break;
+ case PACKAGE: newModes &= ~(PRIVATE); break;
+ case PROTECTED:
+ case PRIVATE:
+ case UNCONDITIONAL: break;
+ default: throw new IllegalArgumentException(modeToDrop + " is not a valid mode to drop");
+ }
+ if (newModes == oldModes) return this; // return self if no change
+ LookupCase l2 = new LookupCase(lookupClass(), prevLookupClass(), newModes);
+ assert((oldModes | newModes) == oldModes); // [A2] (no elevation of access)
+ assert(l2.prevLookupClass() == null || (newModes & MODULE) == 0);
+ return l2;
+ }
+
+ boolean isModuleAccessible(Class<?> c) {
+ Module m1 = lookupClass().getModule();
+ Module m2 = c.getModule();
+ Module m0 = prevLookupClass() != null ? prevLookupClass.getModule() : m1;
+ String pn = c.getPackageName();
+ boolean accessible = m1.canRead(m2) && m2.isExported(pn, m1);
+ if (m1 != m0) {
+ accessible = accessible && m0.canRead(m2) && m2.isExported(pn, m0);
+ }
+ return accessible;
+ }
+
@Override
public String toString() {
String s = lookupClass().getSimpleName();
String lstr = lookupString();
int sl = lstr.indexOf('/');
@@ -227,37 +303,52 @@
/** Predict the success or failure of accessing this method. */
public boolean willAccess(Method m) {
Class<?> c1 = lookupClass();
Class<?> c2 = m.getDeclaringClass();
+ Module m1 = c1.getModule();
+ Module m2 = c2.getModule();
+ Module m0 = prevLookupClass != null ? prevLookupClass.getModule() : m1;
+ // unconditional has access to all public types/members of types that is in a package
+ // are unconditionally exported
+ if ((lookupModes & UNCONDITIONAL) != 0) {
+ return m2.isExported(c2.getPackageName())
+ && Modifier.isPublic(c2.getModifiers())
+ && Modifier.isPublic(m.getModifiers());
+ }
- // publicLookup has access to all public types/members of types in unnamed modules
- if ((lookupModes & UNCONDITIONAL) != 0
- && (lookupModes & PUBLIC) != 0
- && !c2.getModule().isNamed()
+ // c1 and c2 are in different module
+ if (m1 != m2 || m0 != m2) {
+ return (lookupModes & PUBLIC) != 0
+ && isModuleAccessible(c2)
&& Modifier.isPublic(c2.getModifiers())
- && Modifier.isPublic(m.getModifiers()))
- return true;
+ && Modifier.isPublic(m.getModifiers());
+ }
+
+ assert(m1 == m2 && prevLookupClass == null);
+
+ if (!willAccessClass(c2, false))
+ return false;
LookupCase lc = this.in(c2);
- int m1 = lc.lookupModes();
- int m2 = fixMods(m.getModifiers());
+ int modes1 = lc.lookupModes();
+ int modes2 = fixMods(m.getModifiers());
// allow private lookup on nestmates. Otherwise, privacy is strictly enforced
- if (c1 != c2 && ((m2 & PRIVATE) == 0 || !c1.isNestmateOf(c2))) {
- m1 &= ~PRIVATE;
+ if (c1 != c2 && ((modes2 & PRIVATE) == 0 || !c1.isNestmateOf(c2))) {
+ modes1 &= ~PRIVATE;
}
// protected access is sometimes allowed
- if ((m2 & PROTECTED) != 0) {
- int prev = m2;
- m2 |= PACKAGE; // it acts like a package method also
+ if ((modes2 & PROTECTED) != 0) {
+ int prev = modes2;
+ modes2 |= PACKAGE; // it acts like a package method also
if ((lookupModes() & PROTECTED) != 0 &&
c2.isAssignableFrom(c1))
- m2 |= PUBLIC; // from a subclass, it acts like a public method also
+ modes2 |= PUBLIC; // from a subclass, it acts like a public method also
}
if (verbosity >= 2)
- System.out.format("%s willAccess %s m1=0x%h m2=0x%h => %s%n", this, lc, m1, m2, ((m2 & m1) != 0));
- return (m2 & m1) != 0;
+ System.out.format("%s willAccess %s modes1=0x%h modes2=0x%h => %s%n", lookupString(), lc.lookupString(), modes1, modes2, (modes2 & modes1) != 0);
+ return (modes2 & modes1) != 0;
}
/** Predict the success or failure of accessing this class. */
public boolean willAccessClass(Class<?> c2, boolean load) {
Class<?> c1 = lookupClass();
@@ -266,27 +357,39 @@
// not visible
return false;
}
}
- // publicLookup has access to all public types/members of types in unnamed modules
- if ((lookupModes & UNCONDITIONAL) != 0
- && (lookupModes & PUBLIC) != 0
- && (!c2.getModule().isNamed())
- && Modifier.isPublic(c2.getModifiers()))
- return true;
+ Module m1 = c1.getModule();
+ Module m2 = c2.getModule();
+ Module m0 = prevLookupClass != null ? prevLookupClass.getModule() : m1;
+ // unconditional has access to all public types that is in an unconditionally exported package
+ if ((lookupModes & UNCONDITIONAL) != 0) {
+ return m2.isExported(c2.getPackageName()) && Modifier.isPublic(c2.getModifiers());
+ }
+ // c1 and c2 are in different module
+ if (m1 != m2 || m0 != m2) {
+ return (lookupModes & PUBLIC) != 0
+ && isModuleAccessible(c2)
+ && Modifier.isPublic(c2.getModifiers());
+ }
+
+ assert(m1 == m2 && prevLookupClass == null);
LookupCase lc = this.in(c2);
- int m1 = lc.lookupModes();
+ int modes1 = lc.lookupModes();
boolean r = false;
- if (m1 == 0) {
+ if (modes1 == 0) {
r = false;
} else {
- int m2 = fixMods(c2.getModifiers());
- if ((m2 & PUBLIC) != 0) {
+ if (Modifier.isPublic(c2.getModifiers())) {
+ if ((modes1 & MODULE) != 0)
r = true;
- } else if ((m1 & PACKAGE) != 0 && c1.getPackage() == c2.getPackage()) {
+ else if ((modes1 & PUBLIC) != 0)
+ r = m1.isExported(c2.getPackageName());
+ } else {
+ if ((modes1 & PACKAGE) != 0 && c1.getPackage() == c2.getPackage())
r = true;
}
}
if (verbosity >= 2) {
System.out.println(this+" willAccessClass "+lc+" c1="+c1+" c2="+c2+" => "+r);
@@ -326,20 +429,20 @@
LOADERS.add(cl);
}
return i+1;
}
- private void addLookupEdge(LookupCase l1, Class<?> c2, LookupCase l2) {
+ private void addLookupEdge(LookupCase l1, Class<?> c2, LookupCase l2, int dropAccess) {
TreeSet<LookupCase> edges = CASE_EDGES.get(l2);
if (edges == null) CASE_EDGES.put(l2, edges = new TreeSet<>());
if (edges.add(l1)) {
Class<?> c1 = l1.lookupClass();
assert(l2.lookupClass() == c2); // [A1]
int m1 = l1.lookupModes();
int m2 = l2.lookupModes();
assert((m1 | m2) == m1); // [A2] (no elevation of access)
- LookupCase expect = l1.in(c2);
+ LookupCase expect = dropAccess == 0 ? l1.in(c2) : l1.in(c2).dropLookupMode(dropAccess);
if (!expect.equals(l2))
System.out.println("*** expect "+l1+" => "+expect+" but got "+l2);
assertEquals(l2, expect);
}
}
@@ -356,13 +459,18 @@
System.out.println("loaders = "+LOADERS);
int rounds = 0;
for (int lastCount = -1; lastCount != CASES.size(); ) {
lastCount = CASES.size(); // if CASES grow in the loop we go round again
for (LookupCase lc1 : CASES.toArray(new LookupCase[0])) {
+ for (int mode : ACCESS_CASES) {
+ LookupCase lc2 = new LookupCase(lc1.lookup().dropLookupMode(mode));
+ addLookupEdge(lc1, lc1.lookupClass(), lc2, mode);
+ CASES.add(lc2);
+ }
for (Class<?> c2 : classes) {
LookupCase lc2 = new LookupCase(lc1.lookup().in(c2));
- addLookupEdge(lc1, c2, lc2);
+ addLookupEdge(lc1, c2, lc2, 0);
CASES.add(lc2);
}
}
rounds++;
}
@@ -384,22 +492,24 @@
@Test public void test() {
makeCases(lookups());
if (verbosity > 0) {
verbosity += 9;
Method pro_in_self = targetMethod(THIS_CLASS, PROTECTED, methodType(void.class));
- testOneAccess(lookupCase("AccessControlTest/public"), pro_in_self, "find");
- testOneAccess(lookupCase("Remote_subclass/public"), pro_in_self, "find");
+ testOneAccess(lookupCase("AccessControlTest/module"), pro_in_self, "find");
+ testOneAccess(lookupCase("Remote_subclass/module"), pro_in_self, "find");
testOneAccess(lookupCase("Remote_subclass"), pro_in_self, "find");
verbosity -= 9;
}
Set<Class<?>> targetClassesDone = new HashSet<>();
for (LookupCase targetCase : CASES) {
Class<?> targetClass = targetCase.lookupClass();
if (!targetClassesDone.add(targetClass)) continue; // already saw this one
String targetPlace = placeName(targetClass);
if (targetPlace == null) continue; // Object, String, not a target
for (int targetAccess : ACCESS_CASES) {
+ if (targetAccess == MODULE || targetAccess == UNCONDITIONAL)
+ continue;
MethodType methodType = methodType(void.class);
Method method = targetMethod(targetClass, targetAccess, methodType);
// Try to access target method from various contexts.
for (LookupCase sourceCase : CASES) {
testOneAccess(sourceCase, method, "findClass");
@@ -455,11 +565,10 @@
testCount++;
if (!didAccess) testCountFails++;
}
static Method targetMethod(Class<?> targetClass, int targetAccess, MethodType methodType) {
- assert targetAccess != MODULE;
String methodName = accessName(targetAccess)+placeName(targetClass);
if (verbosity >= 2)
System.out.println(targetClass.getSimpleName()+"."+methodName+methodType);
try {
Method method = targetClass.getDeclaredMethod(methodName, methodType.parameterArray());
@@ -489,14 +598,17 @@
case PRIVATE: return "pri_in_";
}
assert(false);
return "?";
}
- // MODULE not a test case at this time
private static final int[] ACCESS_CASES = {
- PUBLIC, PACKAGE, PRIVATE, PROTECTED
+ PUBLIC, PACKAGE, PRIVATE, PROTECTED, MODULE, UNCONDITIONAL
};
+ /*
+ * Adjust PUBLIC => PUBLIC|MODULE|UNCONDITIONAL
+ * Adjust 0 => PACKAGE
+ */
/** Return one of the ACCESS_CASES. */
static int fixMods(int mods) {
mods &= (PUBLIC|PRIVATE|PROTECTED);
switch (mods) {
case PUBLIC: case PRIVATE: case PROTECTED: return mods;
< prev index next >