--- old/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2019-12-03 19:37:28.000000000 -0800
+++ new/src/java.base/share/classes/java/lang/invoke/MethodHandles.java 2019-12-03 19:37:28.000000000 -0800
@@ -25,6 +25,7 @@
package java.lang.invoke;
+import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.module.IllegalAccessLogger;
import jdk.internal.org.objectweb.asm.ClassReader;
@@ -45,8 +46,6 @@
import java.lang.reflect.Modifier;
import java.lang.reflect.ReflectPermission;
import java.nio.ByteOrder;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
@@ -213,6 +212,10 @@
* @see Cross-module lookups
*/
public static Lookup privateLookupIn(Class> targetClass, Lookup caller) throws IllegalAccessException {
+ if (caller.allowedModes == Lookup.TRUSTED) {
+ return new Lookup(targetClass);
+ }
+
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
if (targetClass.isPrimitive())
@@ -1095,8 +1098,8 @@
* and the Core Reflection API
* (as found on {@link java.lang.Class Class}).
*
- * If a security manager is present, member and class lookups are subject to
- * additional checks.
+ * If a security manager is present, member and class lookups are
+ * subject to additional checks.
* From one to three calls are made to the security manager.
* Any of these calls can refuse access by throwing a
* {@link java.lang.SecurityException SecurityException}.
@@ -1140,6 +1143,13 @@
* Therefore, the above rules presuppose a member or class that is public,
* or else that is being accessed from a lookup class that has
* rights to access the member or class.
+ *
+ * If a security manager is present and the current lookup object does not have
+ * private access, then
+ * {@link #defineClass(byte[]) defineClass} and
+ * {@link #defineHiddenClass(byte[], boolean, ClassOption...) defineHiddenClass}
+ * call {@link SecurityManager#checkPermission smgr.checkPermission}
+ * with {@code RuntimePermission("defineClass")} is called.
*
*
Caller sensitive methods
* A small number of Java methods have a special property called caller sensitivity.
@@ -1377,8 +1387,6 @@
*/
Lookup(Class> lookupClass) {
this(lookupClass, null, FULL_POWER_MODES);
- // make sure we haven't accidentally picked up a privileged class:
- checkUnprivilegedlookupClass(lookupClass);
}
private Lookup(Class> lookupClass, Class> prevLookupClass, int allowedModes) {
@@ -1388,6 +1396,7 @@
this.lookupClass = lookupClass;
this.prevLookupClass = prevLookupClass;
this.allowedModes = allowedModes;
+ assert !lookupClass.isPrimitive() && !lookupClass.isArray();
}
private static Lookup newLookup(Class> lookupClass, Class> prevLookupClass, int allowedModes) {
@@ -1485,7 +1494,7 @@
}
// Allow nestmate lookups to be created without special privilege:
if ((newModes & PRIVATE) != 0
- && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) {
+ && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) {
newModes &= ~(PRIVATE|PROTECTED);
}
if ((newModes & (PUBLIC|UNCONDITIONAL)) != 0
@@ -1502,8 +1511,8 @@
* 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} is always
- * dropped and so the resulting lookup mode will never have this access capability.
+ * {@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}
@@ -1589,59 +1598,277 @@
if ((lookupModes() & PACKAGE) == 0)
throw new IllegalAccessException("Lookup does not have PACKAGE access");
assert (lookupModes() & (MODULE|PUBLIC)) != 0;
+ return makeClassDefiner(bytes.clone()).defineClass(false);
+ }
- // parse class bytes to get class name (in internal form)
- bytes = bytes.clone();
- String name;
- try {
- ClassReader reader = new ClassReader(bytes);
- name = reader.getClassName();
- } catch (RuntimeException e) {
- // ASM exceptions are poorly specified
- ClassFormatError cfe = new ClassFormatError();
- cfe.initCause(e);
- throw cfe;
+ private void checkDefineClassPermission() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) return;
+ if (allowedModes == TRUSTED) return;
+
+ if (!hasPrivateAccess()) {
+ sm.checkPermission(new RuntimePermission("defineClass"));
}
+ }
- // get package and class name in binary form
- String cn, pn;
- int index = name.lastIndexOf('/');
- if (index == -1) {
- cn = name;
- pn = "";
- } else {
- cn = name.replace('/', '.');
- pn = cn.substring(0, index);
+ /**
+ * Creates a {@code Lookup} on a hidden class defined to
+ * the same class loader and in the same runtime package and
+ * {@linkplain java.security.ProtectionDomain protection domain} as this
+ * lookup's {@linkplain #lookupClass() lookup class}.
+ *
+ * If {@code options} has {@link ClassOption#NESTMATE NESTMATE} class
+ * option, then the hidden class is added as a member into
+ * {@linkplain Class#getNestHost() the nest} of this lookup's lookup class.
+ *
+ * If {@code options} has {@link ClassOption#WEAK WEAK}, then
+ * the hidden class is weakly referenced from its defining class loader
+ * and may be unloaded while its defining class loader is strongly reachable.
+ *
+ * The hidden class is initialized if the {@code initialize} parameter is
+ * {@code true}.
+ *
+ * A {@link Class#isHiddenClass() hidden} class has the
+ * following additional properties:
+ *
+ * - Naming:
+ * The name of a hidden class returned by {@link Class#getName()} is
+ * defined by the JVM of this form:
+ * {@code + '/' + }
+ * where {@code } is the class name
+ * from the class bytes and {@code } must be an unique unqualified
+ * name (see JVMS 4.2.2)
+ * - Class resolution:
+ * A hidden class cannot be referenced in other classes and cannot be
+ * named as a field type, a method parameter type and a method return type.
+ * It is not discoverable by its class loader for example via
+ * {@link Class#forName(String, boolean, ClassLoader)},
+ * {@link ClassLoader#loadClass(String, boolean)} and also bytecode linkage.
+ *
- Class retransformation:
+ * A hidden class is not {@linkplain java.lang.instrument.Instrumentation#isModifiableClass(Class)
+ * modifiable} by Java agents or tool agents using
+ * the JVM Tool Interface.
+ *
- Serialization:
+ * The default serialization mechanism records the name of a class in
+ * its serialized form and finds the class by name during deserialization.
+ * A serializable hidden class requires a custom serialization
+ * mechanism in order to ensure that instances are properly serialized
+ * and deserialized.
+ *
+ *
+ * The {@linkplain #lookupModes() lookup modes} for this lookup must
+ * have {@code PRIVATE} and {@code MODULE} access to create a hidden class
+ * in the module of this lookup class.
+ *
+ *
The {@code bytes} parameter is the class bytes of a valid class file
+ * (as defined by the The Java Virtual Machine Specification)
+ * with a class name in the same package as the lookup class.
+ *
+ * @param bytes the class bytes
+ * @param initialize if {@code true} the class will be initialized.
+ * @param options {@linkplain ClassOption class options}
+ * @return the {@code Lookup} object on the hidden class
+ *
+ * @throws IllegalArgumentException the bytes are for a class in a different package
+ * to the lookup class
+ * @throws IllegalAccessException if this lookup does not have {@code PRIVATE} and {@code MODULE} access
+ * @throws LinkageError if the class is malformed ({@code ClassFormatError}), cannot be
+ * verified ({@code VerifyError}), is already defined,
+ * or another linkage error occurs
+ * @throws SecurityException if a security manager is present and it
+ * refuses access
+ * @throws NullPointerException if {@code bytes} is {@code null}
+ *
+ * @since 14
+ * @see Class#isHiddenClass()
+ * @jvms 4.2.2 Unqualified Names
+ * @jls 12.3 Linking of Classes and Interfaces
+ * @jls 12.4 Initialization of Classes and Interfaces
+ */
+ public Lookup defineHiddenClass(byte[] bytes, boolean initialize, ClassOption... options)
+ throws IllegalAccessException
+ {
+ Objects.requireNonNull(bytes);
+
+ checkDefineClassPermission();
+ if ((lookupModes() & (PRIVATE|MODULE)) != (PRIVATE|MODULE)){
+ throw new IllegalAccessException(this + " does not have PRIVATE or MODULE access");
+ }
+
+ Set opts = (options != null && options.length > 0) ? Set.of(options) : Set.of();
+ Class> c = makeHiddenClassDefiner(bytes.clone(), opts).defineClass(initialize, null);
+ return new Lookup(c, null, FULL_POWER_MODES);
+ }
+
+ /**
+ * Creates a {@code Lookup} on a hidden class with {@code classData}
+ * defined to the same class loader and in the same runtime package
+ * and {@linkplain java.security.ProtectionDomain protection domain} as
+ * this lookup's {@linkplain #lookupClass() lookup class} with
+ * the given class options.
+ *
+ * This method is equivalent to calling
+ * {@link #defineHiddenClass(byte[], boolean, ClassOption...) defineHiddenClass(bytes, initialize, options)}
+ * as if the hidden class has a private static final unnamed field
+ * pre-initialized with the given {@code classData}.
+ * The {@link MethodHandles#classData(Lookup, String, Class) MethodHandles::classData} method
+ * can be used to retrieve the {@code classData}.
+ *
+ *
The {@linkplain #lookupModes() lookup modes} for this lookup must
+ * have {@code PRIVATE} and {@code MODULE} access in order to create a
+ * hidden class in the module of this lookup class.
+ *
+ * @param bytes the class bytes
+ * @param classData pre-initialized class data
+ * @param initialize if {@code true} the class will be initialized.
+ * @param options {@linkplain ClassOption class options}
+ * @return the {@code Lookup} object on the hidden class
+ *
+ * @throws IllegalArgumentException the bytes are for a class in a different package
+ * to the lookup class
+ * @throws IllegalAccessException if this lookup does not have {@code PRIVATE} and {@code MODULE} access
+ * @throws LinkageError if the class is malformed ({@code ClassFormatError}), cannot be
+ * verified ({@code VerifyError}), is already defined,
+ * or another linkage error occurs
+ * @throws SecurityException if a security manager is present and it
+ * refuses access
+ * @throws NullPointerException if {@code bytes} is {@code null}
+ *
+ * @since 14
+ * @see Lookup#defineHiddenClass(byte[], boolean, ClassOption...)
+ * @see Class#isHiddenClass()
+ */
+ /* package-private */ Lookup defineHiddenClassWithClassData(byte[] bytes, Object classData, boolean initialize, ClassOption... options)
+ throws IllegalAccessException
+ {
+ Objects.requireNonNull(bytes);
+ Objects.requireNonNull(classData);
+
+ checkDefineClassPermission();
+ if ((lookupModes() & (PRIVATE|MODULE)) != (PRIVATE|MODULE)){
+ throw new IllegalAccessException(this + " does not have PRIVATE or MODULE access");
}
- if (!pn.equals(lookupClass.getPackageName())) {
- throw new IllegalArgumentException("Class not in same package as lookup class");
+
+ Set opts = (options != null && options.length > 0) ? Set.of(options) : Set.of();
+ Class> c = makeHiddenClassDefiner(bytes.clone(), opts).defineClass(initialize, classData);
+ return new Lookup(c, null, FULL_POWER_MODES);
+ }
+
+ private ClassDefiner makeClassDefiner(byte[] bytes) {
+ return new ClassDefiner(this, bytes, Set.of(), 0);
+ }
+
+ /**
+ * Returns a ClassDefiner that creates a {@code Class} object of a hidden class
+ * from the given bytes and options.
+ *
+ * @param bytes class bytes
+ * @param options class options
+ * @return ClassDefiner that defines a hidden class of the given bytes and options
+ */
+ ClassDefiner makeHiddenClassDefiner(byte[] bytes, Set options) {
+ return new ClassDefiner(this, bytes, options, HIDDEN_CLASS);
+ }
+
+ /**
+ * This method is only called by MethodHandleImpl.BindCaller.makeInjectedInvoker.
+ *
+ * @param name the name of the class and the name in the class bytes is ignored.
+ * @param bytes class bytes
+ * @return ClassDefiner that defines a hidden class of the given bytes
+ */
+ ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) {
+ return new ClassDefiner(this, name, bytes, HIDDEN_CLASS);
+ }
+
+ static class ClassDefiner {
+ private final Lookup lookup;
+ private final String name;
+ private final byte[] bytes;
+ private final int classFlags;
+
+ // caller should make a defensive copy of the arguments if needed
+ // before calling this constructor
+ private ClassDefiner(Lookup lookup, byte[] bytes, Set options, int flags) {
+ this.lookup = lookup;
+ this.bytes = bytes;
+ this.classFlags = flags | ClassOption.optionsToFlag(options);
+ this.name = className(bytes);
+
+ int index = name.lastIndexOf('.');
+ String pn = (index == -1) ? "" : name.substring(0, index);
+ if (!pn.equals(lookup.lookupClass().getPackageName())) {
+ throw newIllegalArgumentException(name + " not in same package as lookup class: " +
+ lookup.lookupClass().getName());
+ }
}
- // invoke the class loader's defineClass method
- ClassLoader loader = lookupClass.getClassLoader();
- ProtectionDomain pd = (loader != null) ? lookupClassProtectionDomain() : null;
- String source = "__Lookup_defineClass__";
- Class> clazz = SharedSecrets.getJavaLangAccess().defineClass(loader, cn, bytes, pd, source);
- return clazz;
+ // skip package name check
+ private ClassDefiner(Lookup lookup, String name, byte[] bytes, int flags) {
+ this.lookup = lookup;
+ this.bytes = bytes;
+ this.classFlags = flags;
+ this.name = name;
+ }
+
+ String className() {
+ return name;
+ }
+
+ Class> defineClass(boolean initialize) {
+ return defineClass(initialize, null);
+ }
+
+ /**
+ * Defines the class of the given bytes and the given classData.
+ * If {@code initialize} parameter is true, then the class will be initialized.
+ *
+ * @param initialize true if the class to be initialized
+ * @param classData classData or null
+ * @return the class
+ *
+ * @throws LinkageError linkage error
+ */
+ Class> defineClass(boolean initialize, Object classData) {
+ Class> lookupClass = lookup.lookupClass();
+ ClassLoader loader = lookupClass.getClassLoader();
+ ProtectionDomain pd = (loader != null) ? lookup.lookupClassProtectionDomain() : null;
+ Class> c = JLA.defineClass(loader, lookupClass, name, bytes, pd, initialize, classFlags, classData);
+ assert !isNestmate() || c.getNestHost() == lookupClass.getNestHost();
+ return c;
+ }
+
+ private boolean isNestmate() {
+ return (classFlags & NESTMATE_CLASS) != 0;
+ }
+
+ private static String className(byte[] bytes) {
+ try {
+ ClassReader reader = new ClassReader(bytes);
+ String name = reader.getClassName();
+ return name.replace('/', '.');
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ // ASM exceptions are poorly specified
+ ClassFormatError cfe = new ClassFormatError();
+ cfe.initCause(e);
+ throw cfe;
+ }
+ }
}
private ProtectionDomain lookupClassProtectionDomain() {
ProtectionDomain pd = cachedProtectionDomain;
if (pd == null) {
- cachedProtectionDomain = pd = protectionDomain(lookupClass);
+ cachedProtectionDomain = pd = JLA.protectionDomain(lookupClass);
}
return pd;
}
- private ProtectionDomain protectionDomain(Class> clazz) {
- PrivilegedAction pa = clazz::getProtectionDomain;
- return AccessController.doPrivileged(pa);
- }
-
// cached protection domain
private volatile ProtectionDomain cachedProtectionDomain;
-
// Make sure outer class is initialized first.
static { IMPL_NAMES.getClass(); }
@@ -1654,6 +1881,8 @@
*/
static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, null, UNCONDITIONAL);
+ static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
+
private static void checkUnprivilegedlookupClass(Class> lookupClass) {
String name = lookupClass.getName();
if (name.startsWith("java.lang.invoke."))
@@ -1712,11 +1941,11 @@
case PUBLIC|PACKAGE:
case PUBLIC|MODULE|PACKAGE:
return cname + "/package";
- case FULL_POWER_MODES & (~PROTECTED):
- case FULL_POWER_MODES & ~(PROTECTED|MODULE):
- return cname + "/private";
+ case PUBLIC|PACKAGE|PRIVATE:
+ case PUBLIC|MODULE|PACKAGE|PRIVATE:
+ return cname + "/private";
case FULL_POWER_MODES:
- case FULL_POWER_MODES & (~MODULE):
+ case FULL_POWER_MODES & ~(MODULE):
return cname;
case TRUSTED:
return "/trusted"; // internal only; not exported
@@ -3260,6 +3489,53 @@
}
static ConcurrentHashMap LOOKASIDE_TABLE = new ConcurrentHashMap<>();
+
+ /**
+ * The set of class options that specify whether a hidden class created by
+ * {@link Lookup#defineHiddenClass(byte[], boolean, ClassOption...)
+ * Lookup::defineHiddenMethod} method is dynamically added as
+ * a new member to the nest of a lookup class and whether a hidden class
+ * is weakly referenced by its defining class loader.
+ *
+ * @since 14
+ */
+ public enum ClassOption {
+ /**
+ * This class option specifies the hidden class be added to
+ * {@linkplain Class#getNestHost nest} of a lookup class as
+ * a nestmate.
+ *
+ * A hidden nestmate class has access to the private members of all
+ * classes and interfaces in the same nest.
+ *
+ * @see Class#getNestHost()
+ */
+ NESTMATE(NESTMATE_CLASS),
+
+ /**
+ * This class option specifies the hidden class be weakly
+ * referenced by its defining class loader such that it
+ * may be unloaded while its defining class loader is
+ * strongly reachable.
+ *
+ * @jls 12.7 Unloading of Classes and Interfaces
+ */
+ WEAK(WEAK_CLASS);
+
+ /* the flag value is used by VM at define class time */
+ private final int flag;
+ ClassOption(int flag) {
+ this.flag = flag;
+ }
+
+ static int optionsToFlag(Set options) {
+ int flags = 0;
+ for (ClassOption cp : options) {
+ flags |= cp.flag;
+ }
+ return flags;
+ }
+ }
}
/**