/* * Copyright (c) 2012, 2022, 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. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/* @test * @summary test access checking by java.lang.invoke.MethodHandles.Lookup * @compile AccessControlTest.java AccessControlTest_subpkg/Acquaintance_remote.java * @run testng/othervm test.java.lang.invoke.AccessControlTest
*/
/** 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}. * [A1-a] 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> * [A2] If the new lookup class is not the same as the old lookup class, * then {@link #ORIGINAL ORIGINAL} access is lost. * [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.) * [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 old lookup class. * * Other than the above cases, the new lookup will have the same * access capabilities as the original. [A11] * <hr>
*/ public LookupCase in(Class<?> c2) { Class<?> c1 = lookupClass();
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 = (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 ((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) { // no access to c2; lose all access.
changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED|UNCONDITIONAL); // [A7]
} if (!sameClass) {
changed |= ORIGINAL; // [A2]
} if (m2 != m1 && m0 != m1) { // hop to a third module; lose all access
changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED); // [A8]
} if (!sameModule) {
changed |= MODULE; // [A3]
} if (!samePackage) { // Different package; loose PACKAGE and lower access.
changed |= (PACKAGE|PRIVATE|PROTECTED); // [A4]
} if (!sameTopLevel) { // Different top-level class. Lose PRIVATE and PROTECTED access.
changed |= (PRIVATE|PROTECTED); // [A5]
} if (sameClass) { assert(changed == 0); // [A11] (no deprivation if same class)
}
@Override public String toString() {
String s = lookupClass().getSimpleName();
String lstr = lookupString(); int sl = lstr.indexOf('/'); if (sl >= 0) s += lstr.substring(sl);
ClassLoader cld = lookupClass().getClassLoader(); if (cld != THIS_LOADER) s += "/loader#"+numberOf(cld); return s;
}
/** Predict the success or failure of accessing this method. */ publicboolean 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());
}
// 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());
}
assert(m1 == m2 && prevLookupClass == null);
if (!willAccessClass(c2, false)) returnfalse;
LookupCase lc = this.in(c2); int modes1 = lc.lookupModes(); int modes2 = fixMods(m.getModifiers()); // allow private lookup on nestmates. Otherwise, privacy is strictly enforced if (c1 != c2 && ((modes2 & PRIVATE) == 0 || !c1.isNestmateOf(c2))) {
modes1 &= ~PRIVATE;
} // protected access is sometimes allowed if ((modes2 & PROTECTED) != 0) { int prev = modes2;
modes2 |= PACKAGE; // it acts like a package method also if ((lookupModes() & PROTECTED) != 0 &&
c2.isAssignableFrom(c1))
modes2 |= PUBLIC; // from a subclass, it acts like a public method also
} if (verbosity >= 2)
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. */ publicboolean willAccessClass(Class<?> c2, boolean load) { Class<?> c1 = lookupClass(); if (load && c2.getClassLoader() != null) { if (c1.getClassLoader() == null) { // not visible returnfalse;
}
}
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 modes1 = lc.lookupModes(); boolean r = false; if (modes1 == 0) {
r = false;
} else { if (Modifier.isPublic(c2.getModifiers())) { if ((modes1 & MODULE) != 0)
r = true; elseif ((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);
} return r;
}
}
privatefinal TreeSet<LookupCase> CASES = new TreeSet<>(); privatefinal TreeMap<LookupCase,TreeSet<LookupCase>> CASE_EDGES = new TreeMap<>(); privatefinal ArrayList<ClassLoader> LOADERS = new ArrayList<>(); privatefinal ClassLoader THIS_LOADER = this.getClass().getClassLoader();
{ if (THIS_LOADER != null) LOADERS.add(THIS_LOADER); } // #1
private LookupCase lookupCase(String name) { for (LookupCase lc : CASES) { if (lc.toString().equals(name)) return lc;
} thrownew AssertionError(name);
}
privateint numberOf(ClassLoader cl) { if (cl == null) return 0; int i = LOADERS.indexOf(cl); if (i < 0) {
i = LOADERS.size();
LOADERS.add(cl);
} return i+1;
}
privatevoid 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 = 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);
}
}
privatevoid makeCases(Lookup[] originalLookups) { // make initial set of lookup test cases
CASES.clear(); LOADERS.clear(); CASE_EDGES.clear();
ArrayList<Class<?>> classes = new ArrayList<>(); for (Lookup l : originalLookups) {
CASES.add(new LookupCase(l));
classes.remove(l.lookupClass()); // no dups please
classes.add(l.lookupClass());
}
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, 0);
CASES.add(lc2);
}
}
rounds++;
}
System.out.println("filled in "+CASES.size()+" cases from "+originalLookups.length+" original cases in "+rounds+" rounds"); if (false) {
System.out.println("CASES: {"); for (LookupCase lc : CASES) {
System.out.println(lc);
Set<LookupCase> edges = CASE_EDGES.get(lc); if (edges != null) for (LookupCase prev : edges) {
System.out.println("\t"+prev);
}
}
System.out.println("}");
}
}
@Test publicvoid test() {
makeCases(lookups()); if (verbosity > 0) {
verbosity += 9;
Method pro_in_self = targetMethod(THIS_CLASS, PROTECTED, methodType(void.class));
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");
testOneAccess(sourceCase, method, "accessClass");
testOneAccess(sourceCase, method, "find");
testOneAccess(sourceCase, method, "unreflect");
}
}
}
System.out.println("tested "+testCount+" access scenarios; "+testCountFails+" accesses were denied");
}
// This guy tests access from outside the package: /* package test.java.lang.invoke.AccessControlTest_subpkg; public class Acquaintance_remote { public static Lookup[] lookups() { ... } ... }
*/
¤ Dauer der Verarbeitung: 0.18 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.