--- old/src/java.base/share/classes/java/lang/invoke/MemberName.java 2019-07-23 10:53:59.000000000 -0700 +++ new/src/java.base/share/classes/java/lang/invoke/MemberName.java 2019-07-23 10:53:59.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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 @@ -521,7 +521,7 @@ public boolean isAccessibleFrom(Class lookupClass) { int mode = (ALL_ACCESS|MethodHandles.Lookup.PACKAGE|MethodHandles.Lookup.MODULE); return VerifyAccess.isMemberAccessible(this.getDeclaringClass(), this.getDeclaringClass(), flags, - lookupClass, mode); + lookupClass, null, mode); } /** @@ -930,13 +930,21 @@ message += ", from public Lookup"; } else { Module m; + Class plc; if (from instanceof MethodHandles.Lookup) { MethodHandles.Lookup lookup = (MethodHandles.Lookup)from; + from = lookup.lookupClass(); m = lookup.lookupClass().getModule(); + plc = lookup.previousLookupClass(); } else { - m = from.getClass().getModule(); + m = ((Class)from).getModule(); + plc = null; } message += ", from " + from + " (" + m + ")"; + if (plc != null) { + message += ", previous lookup " + + plc.getName() + " (" + plc.getModule() + ")"; + } } } return new IllegalAccessException(message); --- old/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2019-07-23 10:54:00.000000000 -0700 +++ new/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2019-07-23 10:54:00.000000000 -0700 @@ -125,7 +125,7 @@ /** * Returns a {@link Lookup lookup object} which is trusted minimally. - * The lookup has the {@code PUBLIC} and {@code UNCONDITIONAL} modes. + * The lookup has the {@code UNCONDITIONAL} mode. * It can only be used to create method handles to public members of * public classes in packages that are exported unconditionally. *

@@ -134,14 +134,14 @@ * * @apiNote The use of Object is conventional, and because the lookup modes are * limited, there is no special access provided to the internals of Object, its package - * or its module. Consequently, the lookup context of this lookup object will be the - * bootstrap class loader, which means it cannot find user classes. + * or its module. This public lookup object or other lookup object with + * {@code UNCONDITIONAL} mode assumes readability. Consequently, the lookup class + * is not used to determine the lookup context. * *

* Discussion: * The lookup class can be changed to any other class {@code C} using an expression of the form * {@link Lookup#in publicLookup().in(C.class)}. - * but may change the lookup context by virtue of changing the class loader. * A public lookup object is always subject to * security manager checks. * Also, it cannot access @@ -156,64 +156,106 @@ } /** - * Returns a {@link Lookup lookup object} with full capabilities to emulate all - * supported bytecode behaviors, including - * private access, on a target class. - * This method checks that a caller, specified as a {@code Lookup} object, is allowed to - * do deep reflection on the target class. If {@code m1} is the module containing - * the {@link Lookup#lookupClass() lookup class}, and {@code m2} is the module containing - * the target class, then this check ensures that + * Returns a {@link Lookup lookup} object on a target class to emulate all supported + * bytecode behaviors, including private access. + * The returned lookup object can provide access to classes in modules and packages, + * and members of those classes, outside the normal rules of Java access control, + * instead conforming to the more permissive rules for modular deep reflection. + *

+ * A caller, specified as a {@code Lookup} object, in module {@code M1} is + * allowed to do deep reflection on module {@code M2} and package of the target class + * if and only if all of the following conditions are {@code true}: *

*

- * If there is a security manager, its {@code checkPermission} method is called to - * check {@code ReflectPermission("suppressAccessChecks")}. - * @apiNote The {@code MODULE} lookup mode serves to authenticate that the lookup object - * was created by code in the caller module (or derived from a lookup object originally - * created by the caller). A lookup object with the {@code MODULE} lookup mode can be - * shared with trusted parties without giving away {@code PRIVATE} and {@code PACKAGE} - * access to the caller. + * If any of the above checks is violated, this method fails with an + * exception. + *

+ * Otherwise, if {@code M1} and {@code M2} are the same module, this method + * returns a {@code Lookup} on {@code targetClass} with full capabilities and + * {@code null} previous lookup class. + *

+ * Otherwise, {@code M1} and {@code M2} are two different modules. This method + * returns a {@code Lookup} on {@code targetClass} that records + * the lookup class of the caller as the new previous lookup class and + * drops {@code MODULE} access from the full capabilities mode. + * * @param targetClass the target class - * @param lookup the caller lookup object + * @param caller the caller lookup object * @return a lookup object for the target class, with private access - * @throws IllegalArgumentException if {@code targetClass} is a primitve type or array class + * @throws IllegalArgumentException if {@code targetClass} is a primitive type or array class * @throws NullPointerException if {@code targetClass} or {@code caller} is {@code null} - * @throws IllegalAccessException if the access check specified above fails * @throws SecurityException if denied by the security manager + * @throws IllegalAccessException if any of the other access checks specified above fails * @since 9 * @spec JPMS * @see Lookup#dropLookupMode + * @see Cross-module lookups */ - public static Lookup privateLookupIn(Class targetClass, Lookup lookup) throws IllegalAccessException { + public static Lookup privateLookupIn(Class targetClass, Lookup caller) throws IllegalAccessException { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(ACCESS_PERMISSION); if (targetClass.isPrimitive()) throw new IllegalArgumentException(targetClass + " is a primitive class"); if (targetClass.isArray()) throw new IllegalArgumentException(targetClass + " is an array class"); - Module targetModule = targetClass.getModule(); - Module callerModule = lookup.lookupClass().getModule(); - if (!callerModule.canRead(targetModule)) - throw new IllegalAccessException(callerModule + " does not read " + targetModule); - if (targetModule.isNamed()) { - String pn = targetClass.getPackageName(); - assert !pn.isEmpty() : "unnamed package cannot be in named module"; - if (!targetModule.isOpen(pn, callerModule)) - throw new IllegalAccessException(targetModule + " does not open " + pn + " to " + callerModule); + // Ensure that we can reason accurately about private and module access. + if ((caller.lookupModes() & Lookup.PRIVATE) == 0) + throw new IllegalAccessException("caller does not have PRIVATE lookup mode"); + if ((caller.lookupModes() & Lookup.MODULE) == 0) + throw new IllegalAccessException("caller does not have MODULE lookup mode"); + + // previous lookup class is never set if it has MODULE access + assert caller.previousLookupClass() == null; + + Class callerClass = caller.lookupClass(); + Module callerModule = callerClass.getModule(); // M1 + Module targetModule = targetClass.getModule(); // M2 + Class newPreviousClass = null; + int newModes = Lookup.FULL_POWER_MODES; + + if (targetModule != callerModule) { + if (!callerModule.canRead(targetModule)) + throw new IllegalAccessException(callerModule + " does not read " + targetModule); + if (targetModule.isNamed()) { + String pn = targetClass.getPackageName(); + assert !pn.isEmpty() : "unnamed package cannot be in named module"; + if (!targetModule.isOpen(pn, callerModule)) + throw new IllegalAccessException(targetModule + " does not open " + pn + " to " + callerModule); + } + + // M2 != M1, set previous lookup class to M1 and drop MODULE access + newPreviousClass = callerClass; + newModes &= ~Lookup.MODULE; } - if ((lookup.lookupModes() & Lookup.MODULE) == 0) - throw new IllegalAccessException("lookup does not have MODULE lookup mode"); + if (!callerModule.isNamed() && targetModule.isNamed()) { IllegalAccessLogger logger = IllegalAccessLogger.illegalAccessLogger(); if (logger != null) { - logger.logIfOpenedForIllegalAccess(lookup, targetClass); + logger.logIfOpenedForIllegalAccess(caller, targetClass); } } - return new Lookup(targetClass); + return Lookup.newLookup(targetClass, newPreviousClass, newModes); } /** @@ -533,6 +575,514 @@ * whose bytecode behaviors and Java language access permissions * can be reliably determined and emulated by method handles. * + *

Cross-module lookups

+ * When a lookup class in one module {@code M1} accesses a class in another module + * {@code M2}, extra access checking is performed beyond the access mode bits. + * A {@code Lookup} with {@link #PUBLIC} mode and a lookup class in {@code M1} + * can access public types in {@code M2} when {@code M2} is readable to {@code M1} + * and when the type is in a package of {@code M2} that is exported to + * at least {@code M1}. + *

+ * A {@code Lookup} on {@code C} can also teleport to a target class + * via {@link #in(Class) Lookup.in} and {@link MethodHandles#privateLookupIn(Class, Lookup) + * MethodHandles.privateLookupIn} methods. + * Teleporting across modules will always record the original lookup class as + * the {@linkplain #previousLookupClass() previous lookup class} + * and drops {@link Lookup#MODULE MODULE} access. + * If the target class is in the same module as the lookup class {@code C}, + * then the target class becomes the new lookup class + * and there is no change to the previous lookup class. + * If the target class is in a different module from {@code M1} ({@code C}'s module), + * {@code C} becomes the new previous lookup class + * and the target class becomes the new lookup class. + * In that case, if there was already a previous lookup class in {@code M0}, + * and it differs from {@code M1} and {@code M2}, then the resulting lookup + * drops all privileges. + * For example, + *

+     * {@code
+     * Lookup lookup = MethodHandles.lookup();   // in class C
+     * Lookup lookup2 = lookup.in(D.class);
+     * MethodHandle mh = lookup2.findStatic(E.class, "m", MT);
+     * }
+ *

+ * The {@link #lookup()} factory method produces a {@code Lookup} object + * with {@code null} previous lookup class. + * {@link Lookup#in lookup.in(D.class)} transforms the {@code lookup} on class {@code C} + * to class {@code D} without elevation of privileges. + * If {@code C} and {@code D} are in the same module, + * {@code lookup2} records {@code D} as the new lookup class and keeps the + * same previous lookup class as the original {@code lookup}, or + * {@code null} if not present. + *

+ * When a {@code Lookup} teleports from a class + * in one nest to another nest, {@code PRIVATE} access is dropped. + * When a {@code Lookup} teleports from a class in one package to + * another package, {@code PACKAGE} access is dropped. + * When a {@code Lookup} teleports from a class in one module to another module, + * {@code MODULE} access is dropped. + * Teleporting across modules drops the ability to access non-exported classes + * in both the module of the new lookup class and the module of the old lookup class + * and the resulting {@code Lookup} remains only {@code PUBLIC} access. + * A {@code Lookup} can teleport back and forth to a class in the module of + * the lookup class and the module of the previous class lookup. + * Teleporting across modules can only decrease access but cannot increase it. + * Teleporting to some third module drops all accesses. + *

+ * In the above example, if {@code C} and {@code D} are in different modules, + * {@code lookup2} records {@code D} as its lookup class and + * {@code C} as its previous lookup class and {@code lookup2} has only + * {@code PUBLIC} access. {@code lookup2} can teleport to other class in + * {@code C}'s module and {@code D}'s module. + * If class {@code E} is in a third module, {@code lookup2.in(E.class)} creates + * a {@code Lookup} on {@code E} with no access and {@code lookup2}'s lookup + * class {@code D} is recorded as its previous lookup class. + *

+ * Teleporting across modules restricts access to the public types that + * both the lookup class and the previous lookup class can equally access + * (see below). + *

+ * {@link MethodHandles#privateLookupIn(Class, Lookup) MethodHandles.privateLookupIn(T.class, lookup)} + * can be used to teleport a {@code lookup} from class {@code C} to class {@code T} + * and create a new {@code Lookup} with private access + * if the lookup class is allowed to do deep reflection on {@code T}. + * The {@code lookup} must have {@link #MODULE} and {@link #PRIVATE} access + * to call {@code privateLookupIn}. + * A {@code lookup} on {@code C} in module {@code M1} is allowed to do deep reflection + * on all classes in {@code M1}. If {@code T} is in {@code M1}, {@code privateLookupIn} + * produces a new {@code Lookup} on {@code T} with full capabilities. + * A {@code lookup} on {@code C} is also allowed + * to do deep reflection on {@code T} in another module {@code M2} if + * {@code M1} reads {@code M2} and {@code M2} {@link Module#isOpen(String,Module) opens} + * the package containing {@code T} to at least {@code M1}. + * {@code T} becomes the new lookup class and {@code C} becomes the new previous + * lookup class and {@code MODULE} access is dropped from the resulting {@code Lookup}. + * The resulting {@code Lookup} can be used to do member lookup or teleport + * to another lookup class by calling {@link #in Lookup::in}. But + * it cannot be used to obtain another private {@code Lookup} by calling + * {@link MethodHandles#privateLookupIn(Class, Lookup) privateLookupIn} + * because it has no {@code MODULE} access. + * + *

Cross-module access checks

+ * + * A {@code Lookup} with {@link #PUBLIC} or with {@link #UNCONDITIONAL} mode + * allows cross-module access. The access checking is performed with respect + * to both the lookup class and the previous lookup class if present. + *

+ * A {@code Lookup} with {@link #UNCONDITIONAL} mode can access public type + * in all modules when the type is in a package that is {@linkplain Module#isExported(String) + * exported unconditionally}. + *

+ * If a {@code Lookup} on {@code LC} in {@code M1} has no previous lookup class, + * the lookup with {@link #PUBLIC} mode can access all public types in modules + * that are readable to {@code M1} and the type is in a package that is exported + * at least to {@code M1}. + *

+ * If a {@code Lookup} on {@code LC} in {@code M1} has a previous lookup class + * {@code PLC} on {@code M0}, the lookup with {@link #PUBLIC} mode can access + * the intersection of all public types that are accessible to {@code M1} + * with all public types that are accessible to {@code M0}. {@code M0} + * reads {@code M1} and hence the set of accessible types includes: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
+ * Public types in the following packages are accessible to the + * lookup class and the previous lookup class. + *
Equally accessible types to {@code M0} and {@code M1}
unconditional-exported packages from {@code M1}
unconditional-exported packages from {@code M0} if {@code M1} reads {@code M0}
unconditional-exported packages from a third module {@code M2} + * if both {@code M0} and {@code M1} read {@code M2}
qualified-exported packages from {@code M1} to {@code M0}
qualified-exported packages from {@code M0} to {@code M1} + * if {@code M1} reads {@code M0}
qualified-exported packages from a third module {@code M2} to + * both {@code M0} and {@code M1} if both {@code M0} and {@code M1} read {@code M2}
+ * + *

Access modes

+ * + * The table below shows the access modes of a {@code Lookup} produced by + * any of the following factory or transformation methods: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
+ * Access mode summary + *
Lookup objectprotectedprivatepackagemodulepublic
{@code CL = MethodHandles.lookup()} in {@code C}PROPRIPACMOD1R
{@code CL.in(C1)} same packagePACMOD1R
{@code CL.in(C1)} same moduleMOD1R
{@code CL.in(D)} different module2R
{@code CL.in(D).in(C)} hop back to module2R
{@code PRI1 = privateLookupIn(C1,CL)}PROPRIPACMOD1R
{@code PRI1a = privateLookupIn(C,PRI1)}PROPRIPACMOD1R
{@code PRI1.in(C1)} same packagePACMOD1R
{@code PRI1.in(C1)} different packageMOD1R
{@code PRI1.in(D)} different module2R
{@code PRI1.dropLookupMode(PROTECTED)}PRIPACMOD1R
{@code PRI1.dropLookupMode(PRIVATE)}PACMOD1R
{@code PRI1.dropLookupMode(PACKAGE)}MOD1R
{@code PRI1.dropLookupMode(MODULE)}1R
{@code PRI1.dropLookupMode(PUBLIC)}none
{@code PRI2 = privateLookupIn(D,CL)}PROPRIPAC2R
{@code privateLookupIn(D,PRI1)}PROPRIPAC2R
{@code privateLookupIn(C,PRI2)} failsIAE
{@code PRI2.in(D2)} same packagePAC2R
{@code PRI2.in(D2)} different package2R
{@code PRI2.in(C1)} hop back to module2R
{@code PRI2.in(E)} hop to third modulenone
{@code PRI2.dropLookupMode(PROTECTED)}PRIPAC2R
{@code PRI2.dropLookupMode(PRIVATE)}PAC2R
{@code PRI2.dropLookupMode(PACKAGE)}2R
{@code PRI2.dropLookupMode(MODULE)}2R
{@code PRI2.dropLookupMode(PUBLIC)}none
{@code CL.dropLookupMode(PROTECTED)}PRIPACMOD1R
{@code CL.dropLookupMode(PRIVATE)}PACMOD1R
{@code CL.dropLookupMode(PACKAGE)}MOD1R
{@code CL.dropLookupMode(MODULE)}1R
{@code CL.dropLookupMode(PUBLIC)}none
{@code PUB = publicLookup()}U
{@code PUB.in(D)} different moduleU
{@code PUB.in(D).in(E)} third moduleU
{@code PUB.dropLookupMode(UNCONDITIONAL)}none
{@code privateLookupIn(C1,PUB)} failsIAE
{@code ANY.in(X)}, for inaccessible Xnone
+ * + *

+ * Notes: + *

+ * *

Security manager interactions

* Although bytecode instructions can only refer to classes in * a related class loader, this API can search for methods in any @@ -645,6 +1195,9 @@ /** The class on behalf of whom the lookup is being performed. */ private final Class lookupClass; + /** previous lookup class */ + private final Class prevLookupClass; + /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */ private final int allowedModes; @@ -656,6 +1209,10 @@ * which may contribute to the result of {@link #lookupModes lookupModes}. * The value, {@code 0x01}, happens to be the same as the value of the * {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}. + *

+ * A {@code Lookup} with this lookup mode performs cross-module access check + * with respect to the {@linkplain #lookupClass() lookup class} and + * {@linkplain #previousLookupClass() previous lookup class} if present. */ public static final int PUBLIC = Modifier.PUBLIC; @@ -680,7 +1237,7 @@ */ public static final int PACKAGE = Modifier.STATIC; - /** A single-bit mask representing {@code module} access (default access), + /** A single-bit mask representing {@code module} access, * which may contribute to the result of {@link #lookupModes lookupModes}. * The value is {@code 0x10}, which does not correspond meaningfully to * any particular {@linkplain java.lang.reflect.Modifier modifier bit}. @@ -688,6 +1245,10 @@ * with this lookup mode can access all public types in the module of the * lookup class and public types in packages exported by other modules * to the module of the lookup class. + *

+ * If this lookup mode is set, the {@linkplain #previousLookupClass() + * previous lookup class} is always {@code null}. + * * @since 9 * @spec JPMS */ @@ -699,10 +1260,14 @@ * any particular {@linkplain java.lang.reflect.Modifier modifier bit}. * A {@code Lookup} with this lookup mode assumes {@linkplain * java.lang.Module#canRead(java.lang.Module) readability}. - * In conjunction with the {@code PUBLIC} modifier bit, a {@code Lookup} - * with this lookup mode can access all public members of public types - * of all modules where the type is in a package that is {@link + * This lookup mode can access all public members of public types + * of all modules when the type is in a package that is {@link * java.lang.Module#isExported(String) exported unconditionally}. + * + *

+ * If this lookup mode is set, the {@linkplain #previousLookupClass() + * previous lookup class} is always {@code null}. + * * @since 9 * @spec JPMS * @see #publicLookup() @@ -713,24 +1278,55 @@ private static final int FULL_POWER_MODES = (ALL_MODES & ~UNCONDITIONAL); private static final int TRUSTED = -1; + /* + * Adjust PUBLIC => PUBLIC|MODULE|UNCONDITIONAL + * Adjust 0 => PACKAGE + */ private static int fixmods(int mods) { mods &= (ALL_MODES - PACKAGE - MODULE - UNCONDITIONAL); - return (mods != 0) ? mods : (PACKAGE | MODULE | UNCONDITIONAL); + if (Modifier.isPublic(mods)) + mods |= UNCONDITIONAL; + return (mods != 0) ? mods : PACKAGE; } /** Tells which class is performing the lookup. It is this class against * which checks are performed for visibility and access permissions. *

+ * If this lookup object has a {@linkplain #previousLookupClass() previous lookup class}, + * access checks are performed against both the lookup class and the previous lookup class. + *

* The class implies a maximum level of access permission, * but the permissions may be additionally limited by the bitmask * {@link #lookupModes lookupModes}, which controls whether non-public members * can be accessed. * @return the lookup class, on behalf of which this lookup object finds members + * @see Cross-module lookups */ public Class lookupClass() { return lookupClass; } + /** Reports a lookup class in another module that this lookup object + * was previously teleported from, or {@code null}. + *

+ * A {@code Lookup} object produced by the factory methods, such as the + * {@link #lookup() lookup()} and {@link #publicLookup() publicLookup()} method, + * has {@code null} previous lookup class. + * A {@code Lookup} object has a non-null previous lookup class + * when this lookup was teleported from an old lookup class + * in one module to a new lookup class in another module. + * + * @return the lookup class in another module that this lookup object was + * previously teleported from, or {@code null} + * @since 14 + * @see #in(Class) + * @see MethodHandles#privateLookupIn(Class, Lookup) + * @see Cross-module lookups + */ + public Class previousLookupClass() { + return prevLookupClass; + } + // This is just for calling out to MethodHandleImpl. private Class lookupClassOrNull() { return (allowedModes == TRUSTED) ? null : lookupClass; @@ -774,48 +1370,69 @@ * which in turn is called by a method not in this package. */ Lookup(Class lookupClass) { - this(lookupClass, FULL_POWER_MODES); + this(lookupClass, null, FULL_POWER_MODES); // make sure we haven't accidentally picked up a privileged class: checkUnprivilegedlookupClass(lookupClass); } - private Lookup(Class lookupClass, int allowedModes) { + private Lookup(Class lookupClass, Class prevLookupClass, int allowedModes) { + assert prevLookupClass == null || ((allowedModes & MODULE) == 0 + && prevLookupClass.getModule() != lookupClass.getModule()); + this.lookupClass = lookupClass; + this.prevLookupClass = prevLookupClass; this.allowedModes = allowedModes; } + private static Lookup newLookup(Class lookupClass, Class prevLookupClass, int allowedModes) { + // make sure we haven't accidentally picked up a privileged class: + checkUnprivilegedlookupClass(lookupClass); + return new Lookup(lookupClass, prevLookupClass, allowedModes); + } + /** * Creates a lookup on the specified new lookup class. * The resulting object will report the specified * class as its own {@link #lookupClass() lookupClass}. + * *

* 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:

*

+ * The new previous lookup class is chosen as follows: + *

+ *

* The resulting lookup's capabilities for loading classes * (used during {@link #findClass} invocations) * are determined by the lookup class' loader, * which may change due to this operation. - * + *

* @param requestedLookupClass the desired lookup class for the new lookup object * @return a lookup object which reports the desired lookup class, or the same object * if there is no change @@ -823,22 +1440,32 @@ * * @revised 9 * @spec JPMS + * @see #accessClass(Class) + * @see Cross-module lookups */ public Lookup in(Class requestedLookupClass) { Objects.requireNonNull(requestedLookupClass); if (allowedModes == TRUSTED) // IMPL_LOOKUP can make any lookup at all - return new Lookup(requestedLookupClass, FULL_POWER_MODES); + return new Lookup(requestedLookupClass, null, FULL_POWER_MODES); if (requestedLookupClass == this.lookupClass) return this; // keep same capabilities int newModes = (allowedModes & FULL_POWER_MODES); - if (!VerifyAccess.isSameModule(this.lookupClass, requestedLookupClass)) { - // Need to drop all access when teleporting from a named module to another - // module. The exception is publicLookup where PUBLIC is not lost. - if (this.lookupClass.getModule().isNamed() - && (this.allowedModes & UNCONDITIONAL) == 0) + Module fromModule = this.lookupClass.getModule(); + Module targetModule = requestedLookupClass.getModule(); + Class plc = this.previousLookupClass(); + if ((this.allowedModes & UNCONDITIONAL) != 0) { + assert plc == null; + newModes = UNCONDITIONAL; + } else if (fromModule != targetModule) { + if (plc != null && !VerifyAccess.isSameModule(plc, requestedLookupClass)) { + // allow hopping back and forth between fromModule and plc's module + // but not the third module newModes = 0; - else - newModes &= ~(MODULE|PACKAGE|PRIVATE|PROTECTED); + } + // drop MODULE access + newModes &= ~(MODULE|PACKAGE|PRIVATE|PROTECTED); + // teleport from this lookup class + plc = this.lookupClass; } if ((newModes & PACKAGE) != 0 && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) { @@ -849,29 +1476,39 @@ && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) { newModes &= ~(PRIVATE|PROTECTED); } - if ((newModes & PUBLIC) != 0 - && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) { + if ((newModes & (PUBLIC|UNCONDITIONAL)) != 0 + && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, this.prevLookupClass, allowedModes)) { // The requested class it not accessible from the lookup class. // No permissions. newModes = 0; } - - checkUnprivilegedlookupClass(requestedLookupClass); - return new Lookup(requestedLookupClass, newModes); + return newLookup(requestedLookupClass, plc, newModes); } - /** * Creates a lookup on the same lookup class which this lookup object * finds members, but with a lookup mode that has lost the given lookup mode. * The lookup mode to drop is one of {@link #PUBLIC PUBLIC}, {@link #MODULE * MODULE}, {@link #PACKAGE PACKAGE}, {@link #PROTECTED PROTECTED} or {@link #PRIVATE PRIVATE}. - * {@link #PROTECTED PROTECTED} and {@link #UNCONDITIONAL UNCONDITIONAL} are always - * dropped and so the resulting lookup mode will never have these access capabilities. + * {@link #PROTECTED PROTECTED} is always + * dropped and so the resulting lookup mode will never have this access capability. * When dropping {@code PACKAGE} then the resulting lookup will not have {@code PACKAGE} * or {@code PRIVATE} access. When dropping {@code MODULE} then the resulting lookup will * not have {@code MODULE}, {@code PACKAGE}, or {@code PRIVATE} access. If {@code PUBLIC} + * is dropped then the resulting lookup has no access. If {@code UNCONDITIONAL} * is dropped then the resulting lookup has no access. + * + * @apiNote + * A lookup with {@code PACKAGE} but not {@code PRIVATE} mode can safely + * delegate non-public access within the package of the lookup class without + * conferring private access. A lookup with {@code MODULE} but not + * {@code PACKAGE} mode can safely delegate {@code PUBLIC} access within + * the module of the lookup class without conferring package access. + * A lookup with a {@linkplain #previousLookupClass() previous lookup class} + * (and {@code PUBLIC} but not {@code MODULE} mode) can safely delegate access + * to public classes accessible to both the module of the lookup class + * and the module of the previous lookup class. + * * @param modeToDrop the lookup mode to drop * @return a lookup object which lacks the indicated mode, or the same object if there is no change * @throws IllegalArgumentException if {@code modeToDrop} is not one of {@code PUBLIC}, @@ -881,9 +1518,9 @@ */ public Lookup dropLookupMode(int modeToDrop) { int oldModes = lookupModes(); - int newModes = oldModes & ~(modeToDrop | PROTECTED | UNCONDITIONAL); + int newModes = oldModes & ~(modeToDrop | PROTECTED); switch (modeToDrop) { - case PUBLIC: newModes &= ~(ALL_MODES); break; + case PUBLIC: newModes &= ~(FULL_POWER_MODES); break; case MODULE: newModes &= ~(PACKAGE | PRIVATE); break; case PACKAGE: newModes &= ~(PRIVATE); break; case PROTECTED: @@ -892,7 +1529,7 @@ default: throw new IllegalArgumentException(modeToDrop + " is not a valid mode to drop"); } if (newModes == oldModes) return this; // return self if no change - return new Lookup(lookupClass(), newModes); + return newLookup(lookupClass(), previousLookupClass(), newModes); } /** @@ -997,13 +1634,13 @@ static { IMPL_NAMES.getClass(); } /** Package-private version of lookup which is trusted. */ - static final Lookup IMPL_LOOKUP = new Lookup(Object.class, TRUSTED); + static final Lookup IMPL_LOOKUP = new Lookup(Object.class, null, TRUSTED); /** Version of lookup which is trusted minimally. * It can only be used to create method handles to publicly accessible * members in packages that are exported unconditionally. */ - static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, (PUBLIC|UNCONDITIONAL)); + static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, null, UNCONDITIONAL); private static void checkUnprivilegedlookupClass(Class lookupClass) { String name = lookupClass.getName(); @@ -1013,6 +1650,8 @@ /** * Displays the name of the class from which lookups are to be made. + * followed with "/" and the name of the {@linkplain #previousLookupClass() + * previous lookup class} if present. * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.) * If there are restrictions on the access permitted to this lookup, * this is indicated by adding a suffix to the class name, consisting @@ -1020,14 +1659,14 @@ * allowed access, and is chosen as follows: *

- * If none of the above cases apply, it is the case that full - * access (public, module, package, private, and protected) is allowed. + * If none of the above cases apply, it is the case that full access + * (public, module, package, private, and protected) is allowed. * In this case, no suffix is added. * This is true only of an object obtained originally from * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}. @@ -1047,20 +1686,25 @@ @Override public String toString() { String cname = lookupClass.getName(); + if (prevLookupClass != null) + cname += "/" + prevLookupClass.getName(); switch (allowedModes) { case 0: // no privileges return cname + "/noaccess"; + case UNCONDITIONAL: + return cname + "/publicLookup"; case PUBLIC: return cname + "/public"; - case PUBLIC|UNCONDITIONAL: - return cname + "/publicLookup"; case PUBLIC|MODULE: return cname + "/module"; + case PUBLIC|PACKAGE: case PUBLIC|MODULE|PACKAGE: return cname + "/package"; - case FULL_POWER_MODES & ~PROTECTED: - return cname + "/private"; + case FULL_POWER_MODES & (~PROTECTED): + case FULL_POWER_MODES & ~(PROTECTED|MODULE): + return cname + "/private"; case FULL_POWER_MODES: + case FULL_POWER_MODES & (~MODULE): return cname; case TRUSTED: return "/trusted"; // internal only; not exported @@ -1301,24 +1945,75 @@ } /** - * Determines if a class can be accessed from the lookup context defined by this {@code Lookup} object. The - * static initializer of the class is not run. + * Determines if a class can be accessed from the lookup context defined by + * this {@code Lookup} object. The static initializer of the class is not run. *

- * The lookup context here is determined by the {@linkplain #lookupClass() lookup class} and the - * {@linkplain #lookupModes() lookup modes}. + * If the {@code targetClass} is in the same module as the lookup class, + * the lookup class is {@code LC} in module {@code M1} and + * the previous lookup class is in module {@code M0} or + * {@code null} if not present, + * {@code targetClass} is accessible if and only if one of the following is true: + *

* - * @param targetClass the class to be access-checked + *

+ * Otherwise, if this lookup has {@link #UNCONDITIONAL} access, this lookup + * can access public types in all modules when the type is in a package + * that is exported unconditionally. + *

+ * Otherwise, the target class is in a different module from {@code lookupClass}, + * and if this lookup does not have {@code PUBLIC} access, {@code lookupClass} + * is inaccessible. + *

+ * Otherwise, if this lookup has no {@linkplain #previousLookupClass() previous lookup class}, + * {@code M1} is the module containing {@code lookupClass} and + * {@code M2} is the module containing {@code targetClass}, + * then {@code targetClass} is accessible if and only if + *

+ *

+ * Otherwise, if this lookup has a {@linkplain #previousLookupClass() previous lookup class}, + * {@code M1} and {@code M2} are as before, and {@code M0} is the module + * containing the previous lookup class, then {@code targetClass} is accessible + * if and only if one of the following is true: + *

+ *

+ * Otherwise, {@code targetClass} is not accessible. * + * @param targetClass the class to be access-checked * @return the class that has been access-checked - * - * @throws IllegalAccessException if the class is not accessible from the lookup class, using the allowed access - * modes. + * @throws IllegalAccessException if the class is not accessible from the lookup class + * and previous lookup class, if present, using the allowed access modes. * @exception SecurityException if a security manager is present and it * refuses access * @since 9 + * @see Cross-module lookups */ public Class accessClass(Class targetClass) throws IllegalAccessException { - if (!VerifyAccess.isClassAccessible(targetClass, lookupClass, allowedModes)) { + if (!VerifyAccess.isClassAccessible(targetClass, lookupClass, prevLookupClass, allowedModes)) { throw new MemberName(targetClass).makeAccessException("access violation", this); } checkSecurityManager(targetClass, null); @@ -2083,7 +2778,7 @@ boolean isClassAccessible(Class refc) { Objects.requireNonNull(refc); Class caller = lookupClassOrNull(); - return caller == null || VerifyAccess.isClassAccessible(refc, caller, allowedModes); + return caller == null || VerifyAccess.isClassAccessible(refc, caller, prevLookupClass, allowedModes); } /** Check name for an illegal leading "<" character. */ @@ -2220,7 +2915,7 @@ int requestedModes = fixmods(mods); // adjust 0 => PACKAGE if ((requestedModes & allowedModes) != 0) { if (VerifyAccess.isMemberAccessible(refc, m.getDeclaringClass(), - mods, lookupClass(), allowedModes)) + mods, lookupClass(), previousLookupClass(), allowedModes)) return; } else { // Protected members can also be checked as if they were package-private. @@ -2239,9 +2934,10 @@ (defc == refc || Modifier.isPublic(refc.getModifiers()))); if (!classOK && (allowedModes & PACKAGE) != 0) { - classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), FULL_POWER_MODES) && + // ignore previous lookup class to check if default package access + classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), null, FULL_POWER_MODES) && (defc == refc || - VerifyAccess.isClassAccessible(refc, lookupClass(), FULL_POWER_MODES))); + VerifyAccess.isClassAccessible(refc, lookupClass(), null, FULL_POWER_MODES))); } if (!classOK) return "class is not public"; --- old/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java 2019-07-23 10:54:01.000000000 -0700 +++ new/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java 2019-07-23 10:54:01.000000000 -0700 @@ -88,18 +88,20 @@ * @param defc the class in which the proposed member is actually defined * @param mods modifier flags for the proposed member * @param lookupClass the class for which the access check is being made + * @param prevLookupClass the class for which the access check is being made + * @param allowedModes allowed modes * @return true iff the accessing class can access such a member */ public static boolean isMemberAccessible(Class refc, // symbolic ref class Class defc, // actual def class int mods, // actual member mods Class lookupClass, + Class prevLookupClass, int allowedModes) { if (allowedModes == 0) return false; - assert((allowedModes & PUBLIC) != 0 && - (allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED)) == 0); + assert((allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED)) == 0); // The symbolic reference class (refc) must always be fully verified. - if (!isClassAccessible(refc, lookupClass, allowedModes)) { + if (!isClassAccessible(refc, lookupClass, prevLookupClass, allowedModes)) { return false; } // Usually refc and defc are the same, but verify defc also in case they differ. @@ -109,6 +111,7 @@ switch (mods & ALL_ACCESS_MODES) { case PUBLIC: + assert (allowedModes & PUBLIC) != 0 || (allowedModes & UNCONDITIONAL_ALLOWED) != 0; return true; // already checked above case PROTECTED: assert !defc.isInterface(); // protected members aren't allowed in interfaces @@ -175,14 +178,23 @@ * package that is exported to the module that contains D. *

  • C and D are members of the same runtime package. * + * * @param refc the symbolic reference class to which access is being checked (C) * @param lookupClass the class performing the lookup (D) + * @param prevLookupClass the class from which the lookup was teleported or null + * @param allowedModes allowed modes */ - public static boolean isClassAccessible(Class refc, Class lookupClass, + public static boolean isClassAccessible(Class refc, + Class lookupClass, + Class prevLookupClass, int allowedModes) { if (allowedModes == 0) return false; - assert((allowedModes & PUBLIC) != 0 && - (allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED)) == 0); + assert((allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED)) == 0); + + if ((allowedModes & PACKAGE_ALLOWED) != 0 && + isSamePackage(lookupClass, refc)) + return true; + int mods = getClassModifiers(refc); if (isPublic(mods)) { @@ -195,37 +207,62 @@ return true; } - // trivially allow - if ((allowedModes & MODULE_ALLOWED) != 0 && - (lookupModule == refModule)) - return true; + // allow access to public types in all unconditionally exported packages + if ((allowedModes & UNCONDITIONAL_ALLOWED) != 0) { + return refModule.isExported(refc.getPackageName()); + } - // check readability when UNCONDITIONAL not allowed - if (((allowedModes & UNCONDITIONAL_ALLOWED) != 0) - || lookupModule.canRead(refModule)) { - - // check that refc is in an exported package - if ((allowedModes & MODULE_ALLOWED) != 0) { - if (refModule.isExported(refc.getPackageName(), lookupModule)) - return true; - } else { - // exported unconditionally - if (refModule.isExported(refc.getPackageName())) - return true; - } - - // not exported but allow access during VM initialization - // because java.base does not have its exports setup - if (!jdk.internal.misc.VM.isModuleSystemInited()) + if (lookupModule == refModule && prevLookupClass == null) { + // allow access to all public types in lookupModule + if ((allowedModes & MODULE_ALLOWED) != 0) return true; + + assert (allowedModes & PUBLIC) != 0; + return refModule.isExported(refc.getPackageName()); } + // cross-module access + // 1. refc is in different module from lookupModule, or + // 2. refc is in lookupModule and a different module from prevLookupModule + Module prevLookupModule = prevLookupClass != null ? prevLookupClass.getModule() + : null; + assert refModule != lookupModule || refModule != prevLookupModule; + if (isModuleAccessible(refc, lookupModule, prevLookupModule)) + return true; + + // not exported but allow access during VM initialization + // because java.base does not have its exports setup + if (!jdk.internal.misc.VM.isModuleSystemInited()) + return true; + // public class not accessible to lookupClass return false; } - if ((allowedModes & PACKAGE_ALLOWED) != 0 && - isSamePackage(lookupClass, refc)) - return true; + + return false; + } + + /* + * Tests if a class or interface REFC is accessible to m1 and m2 where m2 + * may be null. + * + * A class or interface REFC in m is accessible to m1 and m2 if and only if + * both m1 and m2 read m and m exports the package of REFC at least to + * both m1 and m2. + */ + public static boolean isModuleAccessible(Class refc, Module m1, Module m2) { + Module refModule = refc.getModule(); + assert refModule != m1 || refModule != m2; + int mods = getClassModifiers(refc); + if (isPublic(mods)) { + if (m1.canRead(refModule) && (m2 == null || m2.canRead(refModule))) { + String pn = refc.getPackageName(); + + // refc is exported package to at least both m1 and m2 + if (refModule.isExported(pn, m1) && (m2 == null || refModule.isExported(pn, m2))) + return true; + } + } return false; } --- old/test/jdk/java/lang/invoke/AccessControlTest.java 2019-07-23 10:54:02.000000000 -0700 +++ new/test/jdk/java/lang/invoke/AccessControlTest.java 2019-07-23 10:54:01.000000000 -0700 @@ -1,5 +1,5 @@ /* - * 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 @@ -33,7 +33,6 @@ 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.*; @@ -62,23 +61,28 @@ private class LookupCase implements Comparable { 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 int lookupModes() { return lookupModes; } + 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; } @@ -86,12 +90,24 @@ 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()); } @@ -102,6 +118,7 @@ } public boolean equals(LookupCase that) { return (this.lookupClass() == that.lookupClass() && + this.prevLookupClass() == that.prevLookupClass() && this.lookupModes() == that.lookupModes()); } @@ -113,20 +130,25 @@ /** 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); @@ -138,41 +160,50 @@ * Creates a lookup on the specified new lookup class. * [A1] The resulting object will report the specified * class as its own {@link #lookupClass lookupClass}. - *

    * [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:

    + * [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. + *

    + * 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] *


    */ 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)); @@ -180,40 +211,85 @@ 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); - assert(l2.lookupClass() == c2); // [A1] - assert((m1 | m2) == m1); // [A2] (no elevation of access) + + 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((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(); @@ -229,33 +305,48 @@ 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()); + } + + // 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()); + } - // 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()) - && Modifier.isPublic(m.getModifiers())) - return true; + 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. */ @@ -268,24 +359,36 @@ } } - // 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) { - r = true; - } else if ((m1 & PACKAGE) != 0 && c1.getPackage() == c2.getPackage()) { - r = true; + if (Modifier.isPublic(c2.getModifiers())) { + if ((modes1 & MODULE) != 0) + r = true; + else if ((modes1 & PUBLIC) != 0) + r = m1.isExported(c2.getPackageName()); + } else { + if ((modes1 & PACKAGE) != 0 && c1.getPackage() == c2.getPackage()) + r = true; } } if (verbosity >= 2) { @@ -328,7 +431,7 @@ return i+1; } - private void addLookupEdge(LookupCase l1, Class c2, LookupCase l2) { + private void addLookupEdge(LookupCase l1, Class c2, LookupCase l2, int dropAccess) { TreeSet edges = CASE_EDGES.get(l2); if (edges == null) CASE_EDGES.put(l2, edges = new TreeSet<>()); if (edges.add(l1)) { @@ -337,7 +440,7 @@ 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); @@ -358,9 +461,14 @@ 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); } } @@ -386,8 +494,8 @@ 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; } @@ -398,6 +506,8 @@ 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. @@ -457,7 +567,6 @@ } 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); @@ -491,10 +600,13 @@ 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); --- old/test/jdk/java/lang/invoke/DropLookupModeTest.java 2019-07-23 10:54:02.000000000 -0700 +++ new/test/jdk/java/lang/invoke/DropLookupModeTest.java 2019-07-23 10:54:02.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -106,37 +106,39 @@ assertTrue(lookup.lookupModes() == 0); } + @DataProvider(name = "unconditionals") + public Object[][] unconditionals() { + Lookup publicLookup = MethodHandles.publicLookup(); + return new Object[][] { + { publicLookup, Object.class }, + { publicLookup.in(String.class), String.class }, + { publicLookup.in(DropLookupModeTest.class), DropLookupModeTest.class }, + }; + } + /** - * Test dropLookupMode on the public Lookup. + * Test dropLookupMode on the lookup with public lookup + * and UNCONDITIONAL */ - public void testPublicLookup() { - final Lookup publicLookup = MethodHandles.publicLookup(); - final Class lc = publicLookup.lookupClass(); - assertTrue(publicLookup.lookupModes() == (PUBLIC|UNCONDITIONAL)); - - Lookup lookup = publicLookup.dropLookupMode(PRIVATE); - assertTrue(lookup.lookupClass() == lc); - assertTrue(lookup.lookupModes() == PUBLIC); - - lookup = publicLookup.dropLookupMode(PROTECTED); - assertTrue(lookup.lookupClass() == lc); - assertTrue(lookup.lookupModes() == PUBLIC); - - lookup = publicLookup.dropLookupMode(PACKAGE); - assertTrue(lookup.lookupClass() == lc); - assertTrue(lookup.lookupModes() == PUBLIC); - - lookup = publicLookup.dropLookupMode(MODULE); - assertTrue(lookup.lookupClass() == lc); - assertTrue(lookup.lookupModes() == PUBLIC); - - lookup = publicLookup.dropLookupMode(PUBLIC); - assertTrue(lookup.lookupClass() == lc); + @Test(dataProvider = "unconditionals") + public void testUnconditionalLookup(Lookup unconditionalLookup, Class expected) { + assertTrue(unconditionalLookup.lookupModes() == UNCONDITIONAL); + + assertPublicLookup(unconditionalLookup.dropLookupMode(PRIVATE), expected); + assertPublicLookup(unconditionalLookup.dropLookupMode(PROTECTED), expected); + assertPublicLookup(unconditionalLookup.dropLookupMode(PACKAGE), expected); + assertPublicLookup(unconditionalLookup.dropLookupMode(MODULE), expected); + assertPublicLookup(unconditionalLookup.dropLookupMode(PUBLIC), expected); + + // drop all access + Lookup lookup = unconditionalLookup.dropLookupMode(UNCONDITIONAL); + assertTrue(lookup.lookupClass() == expected); assertTrue(lookup.lookupModes() == 0); + } - lookup = publicLookup.dropLookupMode(UNCONDITIONAL); - assertTrue(lookup.lookupClass() == lc); - assertTrue(lookup.lookupModes() == PUBLIC); + private void assertPublicLookup(Lookup lookup, Class expected) { + assertTrue(lookup.lookupClass() == expected); + assertTrue(lookup.lookupModes() == UNCONDITIONAL); } @DataProvider(name = "badInput") @@ -157,4 +159,4 @@ MethodHandles.lookup().dropLookupMode(modeToDrop); } -} \ No newline at end of file +} --- old/test/jdk/java/lang/invoke/MethodHandles/privateLookupIn/test/p/PrivateLookupInTests.java 2019-07-23 10:54:03.000000000 -0700 +++ new/test/jdk/java/lang/invoke/MethodHandles/privateLookupIn/test/p/PrivateLookupInTests.java 2019-07-23 10:54:03.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -82,6 +82,7 @@ } // Invoke MethodHandles.privateLookupIn with a reduced-power caller + @Test(expectedExceptions = {IllegalAccessException.class}) public void testReducedAccessCallerSameModule() throws Throwable { Lookup caller = MethodHandles.lookup().dropLookupMode(PACKAGE); assertTrue((caller.lookupModes() & PRIVATE) == 0); @@ -89,12 +90,6 @@ assertTrue((caller.lookupModes() & MODULE) != 0); Lookup lookup = MethodHandles.privateLookupIn(nonPublicType, caller); - assertTrue(lookup.lookupClass() == nonPublicType); - assertTrue(lookup.hasPrivateAccess()); - - // use it - MethodHandle mh = lookup.findStaticGetter(nonPublicType, "obj", Object.class); - Object obj = mh.invokeExact(); } // Invoke MethodHandles.privateLookupIn with the public lookup as caller --- old/test/jdk/java/lang/invoke/modules/Unnamed.java 2019-07-23 10:54:04.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/Unnamed.java 2019-07-23 10:54:04.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -21,4 +21,11 @@ * questions. */ -public class Unnamed { } +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +public class Unnamed { + public static Lookup lookup() { + return MethodHandles.lookup(); + } +} --- old/test/jdk/java/lang/invoke/modules/m1/p1/Main.java 2019-07-23 10:54:04.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m1/p1/Main.java 2019-07-23 10:54:04.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -45,7 +45,7 @@ private Class p2_Type2; // m1, not exported private Class q1_Type1; // m2, exported private Class q2_Type2; // m2, not exported - private Class x500NameClass; // java.base, not exported + private Class signalClass; // java.base, not exported private Class unnamedClass; // class in unnamed module @BeforeTest @@ -55,7 +55,7 @@ p2_Type2 = Class.forName("p2.Type2"); q1_Type1 = Class.forName("q1.Type1"); q2_Type2 = Class.forName("q2.Type2"); - x500NameClass = Class.forName("sun.security.x509.X500Name"); + signalClass = Class.forName("jdk.internal.misc.Signal"); unnamedClass = Class.forName("Unnamed"); } catch (ClassNotFoundException e) { throw new AssertionError(e); @@ -105,7 +105,7 @@ // java.base findConstructor(lookup, Object.class, void.class); // [A2] - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); // [A3] + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // [A3] // unnamed findConstructor(lookup, unnamedClass, void.class); // [A3] @@ -130,7 +130,7 @@ // java.base findConstructor(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed findConstructor(lookup, unnamedClass, void.class); @@ -139,51 +139,70 @@ /** * Hop to lookup class in another named module * - * [A0] has no access + * [A0] has PUBLIC access if accessible; otherwise no access + * [A1] old lookup class becomes previous lookup class */ public void testFromNamedToNamedModule() throws Exception { + // m2/q1_Type1 is accessible to m1 whereas m2/q_Type2 is not accessible Lookup lookup = MethodHandles.lookup().in(q1_Type1); - assertTrue(lookup.lookupModes() == 0); // [A0] + assertTrue(lookup.lookupModes() == PUBLIC); // [A0] + assertTrue(lookup.previousLookupClass() == Main.class); // [A1] + + Lookup lookup2 = MethodHandles.lookup().in(q2_Type2); + assertTrue(lookup2.lookupModes() == 0); // [A0] + assertTrue(lookup2.previousLookupClass() == Main.class); // [A1] // m1 findConstructorExpectingIAE(lookup, p1_Type1, void.class); findConstructorExpectingIAE(lookup, p2_Type2, void.class); + findConstructorExpectingIAE(lookup2, p1_Type1, void.class); + findConstructorExpectingIAE(lookup2, p2_Type2, void.class); + // m2 - findConstructorExpectingIAE(lookup, q1_Type1, void.class); + findConstructor(lookup, q1_Type1, void.class); // m2/q1 is exported findConstructorExpectingIAE(lookup, q2_Type2, void.class); + findConstructorExpectingIAE(lookup2, q1_Type1, void.class); + findConstructorExpectingIAE(lookup2, q2_Type2, void.class); + // java.base - findConstructorExpectingIAE(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructor(lookup, Object.class, void.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); + + findConstructorExpectingIAE(lookup2, Object.class, void.class); + findConstructorExpectingIAE(lookup2, signalClass, void.class, String.class); // unnamed findConstructorExpectingIAE(lookup, unnamedClass, void.class); + + findConstructorExpectingIAE(lookup2, unnamedClass, void.class); + } /** * Hop to lookup class in an unnamed module * - * [A0] has no access + * [A0] has PUBLIC access */ public void testFromNamedToUnnamedModule() throws Exception { Lookup lookup = MethodHandles.lookup().in(unnamedClass); - assertTrue(lookup.lookupModes() == 0); // [A0] + assertTrue(lookup.lookupModes() == PUBLIC); // [A0] // m1 - findConstructorExpectingIAE(lookup, p1_Type1, void.class); + findConstructor(lookup, p1_Type1, void.class); // p1 is exported findConstructorExpectingIAE(lookup, p2_Type2, void.class); // m2 - findConstructorExpectingIAE(lookup, q1_Type1, void.class); + findConstructor(lookup, q1_Type1, void.class); findConstructorExpectingIAE(lookup, q2_Type2, void.class); // java.base - findConstructorExpectingIAE(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructor(lookup, Object.class, void.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed - findConstructorExpectingIAE(lookup, unnamedClass, void.class); + findConstructor(lookup, unnamedClass, void.class); } /** @@ -206,7 +225,7 @@ // java.base findConstructor(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed findConstructor(lookup, unnamedClass, void.class); @@ -215,11 +234,11 @@ /** * MethodHandles.publicLookup() * - * [A0] has PUBLIC|UNCONDITIONAL access + * [A0] has UNCONDITIONAL access */ public void testPublicLookup() throws Exception { Lookup lookup = MethodHandles.publicLookup(); - assertTrue(lookup.lookupModes() == (PUBLIC|UNCONDITIONAL)); // A0 + assertTrue(lookup.lookupModes() == UNCONDITIONAL); // A0 // m1 findConstructor(lookup, p1_Type1, void.class); @@ -231,7 +250,7 @@ // java.base findConstructor(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed findConstructor(lookup, unnamedClass, void.class); @@ -239,36 +258,38 @@ /** * Hop from publicLookup to accessible type in java.base + * + * [A0] has UNCONDITIONAL access */ public void testPublicLookupToBaseModule() throws Exception { Lookup lookup = MethodHandles.publicLookup().in(String.class); - assertTrue(lookup.lookupModes() == PUBLIC); // A0 + assertTrue(lookup.lookupModes() == UNCONDITIONAL); // A0 // m1 - findConstructorExpectingIAE(lookup, p1_Type1, void.class); + findConstructor(lookup, p1_Type1, void.class); findConstructorExpectingIAE(lookup, p2_Type2, void.class); // m2 - findConstructorExpectingIAE(lookup, q1_Type1, void.class); + findConstructor(lookup, q1_Type1, void.class); findConstructorExpectingIAE(lookup, q2_Type2, void.class); // java.base findConstructor(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed - findConstructorExpectingIAE(lookup, unnamedClass, void.class); + findConstructor(lookup, unnamedClass, void.class); } /** * Hop from publicLookup to accessible type in named module. * - * [A0] has PUBLIC access + * [A0] has UNCONDITIONAL access */ public void testPublicLookupToAccessibleTypeInNamedModule() throws Exception { Lookup lookup = MethodHandles.publicLookup().in(p1_Type1); - assertTrue(lookup.lookupModes() == PUBLIC); // A0 + assertTrue(lookup.lookupModes() == UNCONDITIONAL); // A0 // m1 findConstructor(lookup, p1_Type1, void.class); @@ -280,7 +301,7 @@ // java.base findConstructor(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed findConstructor(lookup, unnamedClass, void.class); @@ -305,7 +326,7 @@ // java.base findConstructorExpectingIAE(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed findConstructorExpectingIAE(lookup, unnamedClass, void.class); @@ -314,11 +335,11 @@ /** * Teleport from publicLookup to public type in unnamed module * - * [A0] has PUBLIC access + * [A0] has UNCONDITIONAL access */ public void testPublicLookupToUnnamedModule() throws Exception { Lookup lookup = MethodHandles.publicLookup().in(unnamedClass); - assertTrue(lookup.lookupModes() == PUBLIC); // A0 + assertTrue(lookup.lookupModes() == UNCONDITIONAL); // A0 // m1 findConstructor(lookup, p1_Type1, void.class); @@ -330,7 +351,7 @@ // java.base findConstructor(lookup, Object.class, void.class); - findConstructorExpectingIAE(lookup, x500NameClass, void.class, String.class); + findConstructorExpectingIAE(lookup, signalClass, void.class, String.class); // unnamed findConstructor(lookup, unnamedClass, void.class); --- /dev/null 2019-07-23 10:54:05.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/Driver1.java 2019-07-23 10:54:05.000000000 -0700 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 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. + * + * 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 + * @bug 8173978 + * @build m3/* m4/* m5/* Unnamed Unnamed1 + * @run testng/othervm m3/jdk.test.ModuleAccessTest + * @summary Basic test case for module access checks and Lookup.in and + * MethodHandles.privateLookupIn + */ --- /dev/null 2019-07-23 10:54:06.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/Unnamed1.java 2019-07-23 10:54:05.000000000 -0700 @@ -0,0 +1,24 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +public class Unnamed1 { } --- /dev/null 2019-07-23 10:54:06.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m3/c1/C1.java 2019-07-23 10:54:06.000000000 -0700 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package c1; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +public class C1 { + public C1() { } + + public static Lookup lookup() { + return MethodHandles.lookup(); + } +} --- /dev/null 2019-07-23 10:54:07.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m3/c1/C2.java 2019-07-23 10:54:07.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package c1; + +public class C2 { + public C2() { } +} --- /dev/null 2019-07-23 10:54:08.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m3/c2/C3.java 2019-07-23 10:54:07.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package c2; + +public class C3{ + public C3() { } +} --- /dev/null 2019-07-23 10:54:08.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m3/jdk/test/ModuleAccessTest.java 2019-07-23 10:54:08.000000000 -0700 @@ -0,0 +1,568 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +package jdk.test; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import e1.CrackM5Access; + +import static java.lang.invoke.MethodHandles.Lookup.*; +import static org.testng.Assert.*; + +public class ModuleAccessTest { + static ModuleLookup m3; + static ModuleLookup m4; + static ModuleLookup m5; + static Map moduleLookupMap = new HashMap<>(); + static Lookup privLookupIn; + static Lookup privLookupIn2; + static Lookup unnamedLookup; + static Class unnamed; + static Class unnamed1; + + @BeforeTest + public void setup() throws Exception { + m3 = new ModuleLookup("m3", 'C'); + m4 = new ModuleLookup("m4", 'D'); + m5 = new ModuleLookup("m5", 'E'); + moduleLookupMap.put(m3.name(), m3); + moduleLookupMap.put(m4.name(), m4); + moduleLookupMap.put(m5.name(), m5); + + privLookupIn = MethodHandles.privateLookupIn(m3.type2, m3.lookup); + privLookupIn2 = MethodHandles.privateLookupIn(m4.type1, m3.lookup); + + unnamed = Class.forName("Unnamed"); + unnamed1 = Class.forName("Unnamed1"); + unnamedLookup = (Lookup)unnamed.getMethod("lookup").invoke(null); + + // m5 reads m3 + CrackM5Access.addReads(m3.module); + CrackM5Access.addReads(unnamed.getModule()); + } + + @DataProvider(name = "samePackage") + public Object[][] samePackage() throws Exception { + return new Object[][] { + { m3.lookup, m3.type2 }, + { privLookupIn, m3.type1 }, + { privLookupIn2, m4.type2 }, + { unnamedLookup, unnamed1 } + }; + } + + /** + * Test lookup.in(T) where T is in the same package of the lookup class. + * + * [A0] targetClass becomes the lookup class + * [A1] no change in previous lookup class + * [A2] PROTECTED and PRIVATE are dropped + */ + @Test(dataProvider = "samePackage") + public void testLookupInSamePackage(Lookup lookup, Class targetClass) throws Exception { + Class lookupClass = lookup.lookupClass(); + Lookup lookup2 = lookup.in(targetClass); + + assertTrue(lookupClass.getPackage() == targetClass.getPackage()); + assertTrue(lookupClass.getModule() == targetClass.getModule()); + assertTrue(lookup2.lookupClass() == targetClass); // [A0] + assertTrue(lookup2.previousLookupClass() == lookup.previousLookupClass()); // [A1] + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE))); // [A2] + } + + @DataProvider(name = "sameModule") + public Object[][] sameModule() throws Exception { + return new Object[][] { + { m3.lookup, m3.type3}, + { privLookupIn, m3.type3}, + { privLookupIn2, m4.type3} + }; + } + + /** + * Test lookup.in(T) where T is in the same module but different package from the lookup class. + * + * [A0] targetClass becomes the lookup class + * [A1] no change in previous lookup class + * [A2] PROTECTED, PRIVATE and PACKAGE are dropped + */ + @Test(dataProvider = "sameModule") + public void testLookupInSameModule(Lookup lookup, Class targetClass) throws Exception { + Class lookupClass = lookup.lookupClass(); + Lookup lookup2 = lookup.in(targetClass); + + assertTrue(lookupClass.getPackage() != targetClass.getPackage()); + assertTrue(lookupClass.getModule() == targetClass.getModule()); + assertTrue(lookup2.lookupClass() == targetClass); // [A0] + assertTrue(lookup2.previousLookupClass() == lookup.previousLookupClass()); // [A1] + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE))); // [A2] + } + + @DataProvider(name = "anotherModule") + public Object[][] anotherModule() throws Exception { + return new Object[][] { + { m3.lookup, m4.type1, m5, m5.accessibleTypesTo(m3.module, m4.module) }, + { m4.lookup, m5.type2, m3, m3.accessibleTypesTo(m4.module, m5.module) }, + { m3.lookup, m5.type1, m4, m4.accessibleTypesTo(m3.module, m5.module) }, + { m5.lookup, unnamed, m3, m3.accessibleTypesTo(m5.module, unnamed.getModule()) }, + }; + } + + /** + * Test lookup.in(T) where T is in a different module from the lookup class. + * + * [A0] targetClass becomes the lookup class + * [A1] lookup class becomes previous lookup class + * [A2] PROTECTED, PRIVATE, PACKAGE, and MODULE are dropped + * [A3] no access to module internal types in m0 and m1 + * [A4] if m1 reads m0, can access public types in m0; otherwise no access. + * [A5] can access public types in m1 exported to m0 + * [A6] can access public types in m2 exported to m0 and m1 + */ + @Test(dataProvider = "anotherModule") + public void testLookupInAnotherModule(Lookup lookup, Class targetClass, + ModuleLookup m2, Set> otherTypes) throws Exception { + Class lookupClass = lookup.lookupClass(); + Module m0 = lookupClass.getModule(); + Module m1 = targetClass.getModule(); + + assertTrue(m0 != m1); + assertTrue(m0.canRead(m1)); + assertTrue(m1.isExported(targetClass.getPackageName(), m0)); + + Lookup lookup2 = lookup.in(targetClass); + assertTrue(lookup2.lookupClass() == targetClass); // [A0] + assertTrue(lookup2.previousLookupClass() == lookup.lookupClass()); // [A1] + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|MODULE))); // [A2] + + // [A3] no access to module internal type in m0 + // [A4] if m1 reads m0, + // [A4] no access to public types exported from m0 unconditionally + // [A4] no access to public types exported from m0 + ModuleLookup ml0 = moduleLookupMap.get(m0.getName()); + if (m1.canRead(m0)) { + for (Class type : ml0.unconditionalExports()) { + testAccess(lookup2, type); + } + for (Class type : ml0.qualifiedExportsTo(m1)) { + testAccess(lookup2, type); + } + } else { + findConstructorExpectingIAE(lookup2, ml0.type1, void.class); + findConstructorExpectingIAE(lookup2, ml0.type2, void.class); + findConstructorExpectingIAE(lookup2, ml0.type3, void.class); + } + + // [A5] can access public types exported from m1 unconditionally + // [A5] can access public types exported from m1 to m0 + if (m1.isNamed()) { + ModuleLookup ml1 = moduleLookupMap.get(m1.getName()); + assertTrue(ml1.unconditionalExports().size() + ml1.qualifiedExportsTo(m0).size() > 0); + for (Class type : ml1.unconditionalExports()) { + testAccess(lookup2, type); + } + for (Class type : ml1.qualifiedExportsTo(m0)) { + testAccess(lookup2, type); + } + } else { + // unnamed module + testAccess(lookup2, unnamed1); + } + + // [A5] can access public types exported from m2 unconditionally + // [A5] can access public types exported from m2 to m0 and m1 + for (Class type : otherTypes) { + assertTrue(type.getModule() == m2.module); + testAccess(lookup2, type); + } + + // test inaccessible types + for (Class type : Set.of(m2.type1, m2.type2, m2.type3)) { + if (!otherTypes.contains(type)) { + // type is accessible to this lookup + try { + lookup2.accessClass(type); + assertTrue(false); + } catch (IllegalAccessException e) {} + + findConstructorExpectingIAE(lookup2, type, void.class); + } + } + } + + public void testAccess(Lookup lookup, Class type) throws Exception { + // type is accessible to this lookup + assertTrue(lookup.accessClass(type) == type); + + // can find constructor + findConstructor(lookup, type, void.class); + + Module m0 = lookup.previousLookupClass().getModule(); + Module m1 = lookup.lookupClass().getModule(); + Module m2 = type.getModule(); + + assertTrue(m0 != m1 && m0 != null); + assertTrue((lookup.lookupModes() & MODULE) == 0); + assertTrue(m0 != m2 || m1 != m2); + + MethodHandles.Lookup lookup2 = lookup.in(type); + if (m2 == m1) { + // the same module of the lookup class + assertTrue(lookup2.lookupClass() == type); + assertTrue(lookup2.previousLookupClass() == lookup.previousLookupClass()); + } else if (m2 == m0) { + // hop back to the module of the previous lookup class + assertTrue(lookup2.lookupClass() == type); + assertTrue(lookup2.previousLookupClass() == lookup.lookupClass()); + } else { + // hop to a third module + assertTrue(lookup2.lookupClass() == type); + assertTrue(lookup2.previousLookupClass() == lookup.lookupClass()); + assertTrue(lookup2.lookupModes() == 0); + } + } + + @DataProvider(name = "thirdModule") + public Object[][] thirdModule() throws Exception { + return new Object[][] { + { m3.lookup, m4.type1, m5.type1}, + { m3.lookup, m4.type2, m5.type1}, + { unnamedLookup, m3.type1, m4.type1 }, + }; + } + + /** + * Test lookup.in(c1).in(c2) where c1 is in second module and c2 is in a third module. + * + * [A0] c2 becomes the lookup class + * [A1] c1 becomes previous lookup class + * [A2] all access bits are dropped + */ + @Test(dataProvider = "thirdModule") + public void testLookupInThirdModule(Lookup lookup, Class c1, Class c2) throws Exception { + Class c0 = lookup.lookupClass(); + Module m0 = c0.getModule(); + Module m1 = c1.getModule(); + Module m2 = c2.getModule(); + + assertTrue(m0 != m1 && m0 != m2 && m1 != m2); + assertTrue(m0.canRead(m1) && m0.canRead(m2)); + assertTrue(m1.canRead(m2)); + assertTrue(m1.isExported(c1.getPackageName(), m0)); + assertTrue(m2.isExported(c2.getPackageName(), m0) && m2.isExported(c2.getPackageName(), m1)); + + Lookup lookup1 = lookup.in(c1); + assertTrue(lookup1.lookupClass() == c1); + assertTrue(lookup1.previousLookupClass() == c0); + assertTrue(lookup1.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|MODULE))); + + Lookup lookup2 = lookup1.in(c2); + assertTrue(lookup2.lookupClass() == c2); // [A0] + assertTrue(lookup2.previousLookupClass() == c1); // [A1] + assertTrue(lookup2.lookupModes() == 0, lookup2.toString()); // [A2] + } + + @DataProvider(name = "privLookupIn") + public Object[][] privLookupIn() throws Exception { + return new Object[][] { + { m3.lookup, m4.type1 }, + { m3.lookup, m5.type1 }, + { m4.lookup, m5.type2 }, + { m5.lookup, m3.type3 }, + { m5.lookup, unnamed } + }; + } + + /** + * Test privateLookupIn(T, lookup) where T is in another module + * + * [A0] full capabilities except MODULE bit + * [A1] target class becomes the lookup class + * [A2] the lookup class becomes previous lookup class + * [A3] IAE thrown if lookup has no MODULE access + */ + @Test(dataProvider = "privLookupIn") + public void testPrivateLookupIn(Lookup lookup, Class targetClass) throws Exception { + Module m0 = lookup.lookupClass().getModule(); + Module m1 = targetClass.getModule(); + + // privateLookupIn from m0 to m1 + assertTrue(m0 != m1); + assertTrue(m1.isOpen(targetClass.getPackageName(), m0)); + Lookup privLookup1 = MethodHandles.privateLookupIn(targetClass, lookup); + assertTrue(privLookup1.lookupModes() == (PROTECTED|PRIVATE|PACKAGE|PUBLIC)); // [A0] + assertTrue(privLookup1.lookupClass() == targetClass); // [A1] + assertTrue(privLookup1.previousLookupClass() == lookup.lookupClass()); // [A2] + + // privLookup1 has no MODULE access; can't do privateLookupIn + try { + Lookup privLookup2 = MethodHandles.privateLookupIn(targetClass, privLookup1); // [A3] + assertFalse(privLookup2 != null); + } catch (IllegalAccessException e) {} + } + + /** + * Test member access from the Lookup returned from privateLookupIn + */ + @Test + public void testPrivateLookupAccess() throws Exception { + Class staticsClass = e1.Statics.class; + Lookup privLookup1 = MethodHandles.privateLookupIn(staticsClass, m4.lookup); + assertTrue((privLookup1.lookupModes() & MODULE) == 0); + assertTrue(privLookup1.lookupClass() == staticsClass); + assertTrue(privLookup1.previousLookupClass() == m4.lookup.lookupClass()); + + // access private member and default package member in m5 + MethodType mtype = MethodType.methodType(void.class); + MethodHandle mh1 = privLookup1.findStatic(staticsClass, "privateMethod", mtype); + MethodHandle mh2 = privLookup1.findStatic(staticsClass, "packageMethod", mtype); + + // access public member in exported types from m5 to m4 + findConstructor(privLookup1, m5.type1, void.class); + // no access to public member in non-exported types to m5 + findConstructorExpectingIAE(privLookup1, m5.type3, void.class); + + // no access to public types in m4 since m5 does not read m4 + assertFalse(m5.module.canRead(m4.module)); + findConstructorExpectingIAE(privLookup1, m4.type1, void.class); + + // teleport from a privateLookup to another class in the same package + // lose private access + Lookup privLookup2 = MethodHandles.privateLookupIn(m5.type1, m4.lookup); + Lookup lookup = privLookup2.in(staticsClass); + assertTrue((lookup.lookupModes() & PRIVATE) == 0); + MethodHandle mh3 = lookup.findStatic(staticsClass, "packageMethod", mtype); + try { + lookup.findStatic(staticsClass, "privateMethod", mtype); + assertTrue(false); + } catch (IllegalAccessException e) {} + } + + /** + * Test member access from the Lookup returned from privateLookupIn and + * the lookup mode after dropLookupMode + */ + @Test + public void testDropLookupMode() throws Exception { + Lookup lookup = MethodHandles.privateLookupIn(m5.type1, m4.lookup); + assertTrue((lookup.lookupModes() & MODULE) == 0); + + Lookup lookup1 = lookup.dropLookupMode(PRIVATE); + assertTrue(lookup1.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE))); + Lookup lookup2 = lookup.dropLookupMode(PACKAGE); + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE))); + Lookup lookup3 = lookup.dropLookupMode(MODULE); + assertTrue(lookup3.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE))); + Lookup lookup4 = lookup.dropLookupMode(PUBLIC); + assertTrue(lookup4.lookupModes() == 0); + + } + + /** + * Test no access to a public member on a non-public class + */ + @Test + public void testPrivateLookupOnNonPublicType() throws Exception { + // privateLookup in a non-public type + Class nonPUblicType = Class.forName("e1.NonPublic"); + Lookup privLookup = MethodHandles.privateLookupIn(nonPUblicType, m4.lookup); + MethodType mtype = MethodType.methodType(void.class); + MethodHandle mh1 = privLookup.findStatic(nonPUblicType, "publicStatic", mtype); + + // drop MODULE access i.e. only PUBLIC access + Lookup lookup = privLookup.dropLookupMode(MODULE); + assertTrue(lookup.lookupModes() == PUBLIC); + try { + MethodHandle mh2 = lookup.findStatic(nonPUblicType, "publicStatic", mtype); + assertFalse(mh2 != null); + } catch (IllegalAccessException e) {} + } + + @Test + public void testPublicLookup() { + Lookup publicLookup = MethodHandles.publicLookup(); + Lookup pub1 = publicLookup.in(m3.type1); + Lookup pub2 = pub1.in(java.lang.String.class); + Lookup pub3 = pub2.in(java.lang.management.ThreadMXBean.class); + Lookup pub4 = pub3.dropLookupMode(UNCONDITIONAL); + + assertTrue(publicLookup.lookupClass() == Object.class); + assertTrue(publicLookup.lookupModes() == UNCONDITIONAL); + assertTrue(pub1.lookupClass() == m3.type1); + assertTrue(pub1.lookupModes() == UNCONDITIONAL); + assertTrue(pub2.lookupClass() == String.class); + assertTrue(pub2.lookupModes() == UNCONDITIONAL); + assertTrue(pub3.lookupClass() == java.lang.management.ThreadMXBean.class); + assertTrue(pub3.lookupModes() == UNCONDITIONAL); + assertTrue(pub4.lookupModes() == 0); + + // publicLookup has no MODULE access; can't do privateLookupIn + try { + Lookup pub5 = MethodHandles.privateLookupIn(m4.type1, pub1); + assertFalse(pub5 != null); + } catch (IllegalAccessException e) {} + } + + static class ModuleLookup { + private final Module module; + private final Set packages; + private final Lookup lookup; + private final Class type1; + private final Class type2; + private final Class type3; + + ModuleLookup(String mn, char c) throws Exception { + this.module = ModuleLayer.boot().findModule(mn).orElse(null); + assertNotNull(this.module); + this.packages = module.getDescriptor().packages(); + assertTrue(packages.size() <= 3); + Lookup lookup = null; + Class type1 = null; + Class type2 = null; + Class type3 = null; + for (String pn : packages) { + char n = pn.charAt(pn.length() - 1); + switch (n) { + case '1': + type1 = Class.forName(pn + "." + c + "1"); + type2 = Class.forName(pn + "." + c + "2"); + Method m = type1.getMethod("lookup"); + lookup = (Lookup) m.invoke(null); + break; + case '2': + type3 = Class.forName(pn + "." + c + "3"); + break; + + default: + } + } + this.lookup = lookup; + this.type1 = type1; + this.type2 = type2; + this.type3 = type3; + } + + String name() { + return module.getName(); + } + + /* + * Returns the set of types that are unconditionally exported. + */ + Set> unconditionalExports() { + return Stream.of(type1, type2, type3) + .filter(c -> module.isExported(c.getPackageName())) + .collect(Collectors.toSet()); + } + + /* + * Returns the set of types that are qualifiedly exported to the specified + * caller module + */ + Set> qualifiedExportsTo(Module caller) { + if (caller.canRead(this.module)) { + return Stream.of(type1, type2, type3) + .filter(c -> !module.isExported(c.getPackageName()) + && module.isExported(c.getPackageName(), caller)) + .collect(Collectors.toSet()); + } else { + return Set.of(); + } + } + + /* + * Returns the set of types that are qualifiedly exported to the specified + * caller module + */ + Set> accessibleTypesTo(Module m0, Module m1) { + if (m0.canRead(this.module) && m1.canRead(this.module)) { + return Stream.of(type1, type2, type3) + .filter(c -> module.isExported(c.getPackageName(), m0) + && module.isExported(c.getPackageName(), m1)) + .collect(Collectors.toSet()); + } else { + return Set.of(); + } + } + + /* + * Returns the set of types that are open to the specified caller + * unconditionally or qualifiedly. + */ + Set> opensTo(Module caller) { + if (caller.canRead(this.module)) { + return Stream.of(type1, type2, type3) + .filter(c -> module.isOpen(c.getPackageName(), caller)) + .collect(Collectors.toSet()); + } else { + return Set.of(); + } + } + + public String toString() { + return module.toString(); + } + } + + /** + * Invokes Lookup findConstructor with a method type constructed from the + * given return and parameter types, expecting IllegalAccessException to be + * thrown. + */ + static void findConstructorExpectingIAE(Lookup lookup, + Class clazz, + Class rtype, + Class... ptypes) throws Exception { + try { + MethodHandle mh = findConstructor(lookup, clazz, rtype, ptypes); + assertTrue(false); + } catch (IllegalAccessException expected) { } + } + + /** + * Invokes Lookup findConstructor with a method type constructored from the + * given return and parameter types. + */ + static MethodHandle findConstructor(Lookup lookup, + Class clazz, + Class rtype, + Class... ptypes) throws Exception { + MethodType mt = MethodType.methodType(rtype, ptypes); + return lookup.findConstructor(clazz, mt); + } +} --- /dev/null 2019-07-23 10:54:09.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m3/module-info.java 2019-07-23 10:54:08.000000000 -0700 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +module m3 { + requires m4; + requires m5; + requires testng; + requires java.management; + exports c1; + opens c2 to m5; + exports jdk.test; +} --- /dev/null 2019-07-23 10:54:09.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m4/d1/D1.java 2019-07-23 10:54:09.000000000 -0700 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package d1; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +public class D1 { + public D1() { } + + public static Lookup lookup() { + return MethodHandles.lookup(); + } +} --- /dev/null 2019-07-23 10:54:10.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m4/d1/D2.java 2019-07-23 10:54:10.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package d1; + +public class D2 { + public D2() { } +} --- /dev/null 2019-07-23 10:54:11.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m4/d2/D3.java 2019-07-23 10:54:10.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package d2; + +public class D3 { + public D3() { } +} --- /dev/null 2019-07-23 10:54:11.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m4/module-info.java 2019-07-23 10:54:11.000000000 -0700 @@ -0,0 +1,28 @@ +/* + * Copyright (c) 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. + * + * 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. + */ + +module m4 { + requires m5; + opens d1; + exports d2 to m3; +} --- /dev/null 2019-07-23 10:54:12.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/e1/CrackM5Access.java 2019-07-23 10:54:12.000000000 -0700 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package e1; + +public class CrackM5Access { + private static void privateMethod() { } + + static void packageMethod() { } + + public static void addReads(Module m) { + CrackM5Access.class.getModule().addReads(m); + } +} --- /dev/null 2019-07-23 10:54:13.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/e1/E1.java 2019-07-23 10:54:12.000000000 -0700 @@ -0,0 +1,34 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package e1; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; + +public class E1 { + public E1() { } + + public static Lookup lookup() { + return MethodHandles.lookup(); + } +} --- /dev/null 2019-07-23 10:54:13.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/e1/E2.java 2019-07-23 10:54:13.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package e1; + +public class E2 { + public E2() { } +} --- /dev/null 2019-07-23 10:54:14.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/e1/NonPublic.java 2019-07-23 10:54:14.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package e1; + +class NonPublic { + public static void publicStatic() { } +} --- /dev/null 2019-07-23 10:54:15.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/e1/Statics.java 2019-07-23 10:54:14.000000000 -0700 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package e1; + +public class Statics { + private static void privateMethod() { } + + static void packageMethod() { } +} --- /dev/null 2019-07-23 10:54:15.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/e2/E3.java 2019-07-23 10:54:15.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +package e2; + +public class E3 { + public E3() { } +} --- /dev/null 2019-07-23 10:54:16.000000000 -0700 +++ new/test/jdk/java/lang/invoke/modules/m5/module-info.java 2019-07-23 10:54:16.000000000 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 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. + * + * 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. + */ +module m5 { + exports e1 to m3; + opens e1 to m3, m4; + exports e2 to m3; +}