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

+ * + *

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; + } + } } /**