< prev index next >
src/java.base/share/classes/java/lang/invoke/MethodHandles.java
Print this page
rev 58565 : 8238358: Implementation of JEP 371: Hidden Classes
Reviewed-by: duke
Contributed-by: mandy.chung@oracle.com, lois.foltan@oracle.com, david.holmes@oracle.com, harold.seigel@oracle.com, serguei.spitsyn@oracle.com, alex.buckley@oracle.com, jamsheed.c.m@oracle.com
rev 58566 : [mq]: hidden-class-3-detla
rev 58567 : [mq]: rename-isHidden
rev 58569 : imported patch hidden-class-3-jdarcy
@@ -571,11 +571,11 @@
* <p>
* The JVM imposes a similar requirement on {@code invokespecial} instruction,
* that the receiver argument must match both the resolved method <em>and</em>
* the current class. Again, this requirement is enforced by narrowing the
* type of the leading parameter to the resulting method handle.
- * (See the Java Virtual Machine Specification, section {@jmvs 4.10.1.9}.)
+ * (See the Java Virtual Machine Specification, section {@jvms 4.10.1.9}.)
* <p>
* The JVM represents constructors and static initializer blocks as internal methods
* with special names ({@code "<init>"} and {@code "<clinit>"}).
* The internal syntax of invocation instructions allows them to refer to such internal
* methods as if they were normal methods, but the JVM bytecode verifier rejects them.
@@ -1657,15 +1657,16 @@
* #hasFullPrivilegeAccess() full privilege access}, its {@code checkPermission} method
* is first called to check {@code RuntimePermission("defineClass")}. </p>
*
* @param bytes the class bytes
* @return the {@code Class} object for the class
+ * @throws IllegalAccessException if this lookup does not have {@code PACKAGE} access
+ * @throws ClassFormatError if {@code bytes} is not a {@code ClassFile} structure
* @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 PACKAGE} access
- * @throws LinkageError if the class is malformed ({@code ClassFormatError}), cannot be
- * verified ({@code VerifyError}), is already defined, or another linkage error occurs
+ * @throws VerifyError if the newly created class cannot be verified
+ * @throws LinkageError if the newly created class cannot be linked for any other reason
* @throws SecurityException if a security manager is present and it
* <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
* @throws NullPointerException if {@code bytes} is {@code null}
* @since 9
* @spec JPMS
@@ -1689,10 +1690,63 @@
sm.checkPermission(new RuntimePermission("defineClass"));
}
}
/**
+ * The set of class options that specify whether a hidden class created by
+ * {@link Lookup#defineHiddenClass(byte[], boolean, ClassOption...)
+ * Lookup::defineHiddenClass} method is dynamically added as a new member
+ * to the nest of a lookup class and/or whether a hidden class has
+ * a strong relationship with the class loader marked as its defining loader.
+ *
+ * @since 15
+ */
+ public enum ClassOption {
+ /**
+ * Specifies that a hidden class be added to {@linkplain Class#getNestHost nest}
+ * of a lookup class as a nestmate.
+ *
+ * <p> 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),
+
+ /**
+ * Specifies that a hidden class has a <em>strong</em>
+ * relationship with the class loader marked as its defining loader,
+ * as a normal class or interface has with its own defining loader.
+ * This means that the hidden class may be unloaded if and only if
+ * its defining loader is not reachable and thus may be reclaimed
+ * by a garbage collector (JLS 12.7).
+ *
+ * <p> By default, a hidden class or interface may be unloaded
+ * even if the class loader that is marked as its defining loader is
+ * <a href="../ref/package.html#reachability">reachable</a>.
+
+ *
+ * @jls 12.7 Unloading of Classes and Interfaces
+ */
+ STRONG(STRONG_LOADER_LINK);
+
+ /* 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<ClassOption> options) {
+ int flags = 0;
+ for (ClassOption cp : options) {
+ flags |= cp.flag;
+ }
+ return flags;
+ }
+ }
+
+ /**
* Creates a <em>hidden</em> class or interface from {@code bytes},
* returning a {@code Lookup} on the newly created class or interface.
*
* <p> Ordinarily, a class or interface {@code C} is created by a class loader,
* which either defines {@code C} directly or delegates to another class loader.
@@ -1737,35 +1791,35 @@
* refers to the new {@code CONSTANT_Utf8_info} structure.
*
* <p> Let {@code L} be the defining class loader of the lookup class of this {@code Lookup}.
*
* <p> {@code C} is derived with name {@code CN}, class loader {@code L}, and
- * purported representation {@code newBytes} as if by the rules of JVMS 5.3.5,
+ * purported representation {@code newBytes} as if by the rules of JVMS {@jvms 5.3.5},
* with the following adjustments:
* <ul>
* <li> The constant indicated by {@code this_class} is permitted to specify a name
* that includes a single {@code "."} character, even though this is not a valid
* binary class or interface name in internal form.</li>
*
* <li> The Java Virtual Machine marks {@code L} as the defining class loader of {@code C},
* but no class loader is recorded as an initiating class loader of {@code C}.</li>
*
- * <li> {@code C} is considered to have the same runtime package and
- * {@linkplain java.security.ProtectionDomain protection domain}
+ * <li> {@code C} is considered to have the same runtime
+ * {@linkplain Class#getPackage() package}, {@linkplain Class#getModule() module}
+ * and {@linkplain java.security.ProtectionDomain protection domain}
* as the lookup class of this {@code Lookup}.
- *
* <li> Let {@code GN} be the binary name obtained by taking {@code N}
* (a binary name encoded in internal form) and replacing ASCII forward slashes with
* ASCII periods. For the instance of {@link java.lang.Class} representing {@code C},
* {@link Class#getName()} returns the string {@code GN + "/" + <suffix>}, even though
* this is not a valid binary class or interface name.</li>
* </ul>
* </li>
* </ol>
*
* <p> After {@code C} is derived, it is linked by the Java Virtual Machine.
- * Linkage occurs as specified in JVMS 5.4.3, with the following adjustments:
+ * Linkage occurs as specified in JVMS {@jvms 5.4.3}, with the following adjustments:
* <ul>
* <li> During verification, whenever it is necessary to load the class named
* {@code CN}, the attempt succeeds, producing class {@code C}. No request is
* made of any class loader.</li>
*
@@ -1775,19 +1829,18 @@
* </ul>
*
* <p> If the {@code initialize} parameter is {@code true},
* then {@code C} is initialized by the Java Virtual Machine.
*
- * <p> The newly created class or interface {@code C} is <em>hidden</em>, in the sense that
+ * <p> The newly created class or interface {@code C} serves as the
+ * {@linkplain #lookupClass() lookup class} of the {@code Lookup} object
+ * returned by this method. {@code C} is <em>hidden</em> in the sense that
* no other class or interface can refer to {@code C} via a constant pool entry.
* That is, a hidden class or interface cannot be named as a supertype, a field type,
* a method parameter type, or a method return type by any other class.
* This is because a hidden class or interface does not have a binary name, so
* there is no internal form available to record in any class's constant pool.
- * (Given the {@code Lookup} object returned this method, its lookup class
- * is a {@code Class} object for which {@link Class#getName()} returns a string
- * that is not a binary name.)
* A hidden class or interface is not discoverable by {@link Class#forName(String, boolean, ClassLoader)},
* {@link ClassLoader#loadClass(String, boolean)}, or {@link #findClass(String)}, and
* is not {@linkplain java.lang.instrument.Instrumentation#isModifiableClass(Class)
* modifiable} by Java agents or tool agents using the <a href="{@docRoot}/../specs/jvmti.html">
* JVM Tool Interface</a>.
@@ -1826,12 +1879,12 @@
* In contrast, if {@code STRONG} is used, then the JVM may run out of memory,
* just as if normal classes were created by class loaders.
*
* <p> Classes and interfaces in a nest are allowed to have mutual access to
* their private members. The nest relationship is determined by
- * the {@code NestHost} attribute (JVMS 4.7.28) and
- * the {@code NestMembers} attribute (JVMS 4.7.29) in a {@code class} file.
+ * the {@code NestHost} attribute (JVMS {@jvms 4.7.28}) and
+ * the {@code NestMembers} attribute (JVMS {@jvms 4.7.29}) in a {@code class} file.
* By default, a hidden class belongs to a nest consisting only of itself
* because a hidden class has no binary name.
* The {@link ClassOption#NESTMATE NESTMATE} option can be passed in {@code options}
* to create a hidden class or interface {@code C} as a member of a nest.
* The nest to which {@code C} belongs is not based on any {@code NestHost} attribute
@@ -1839,11 +1892,11 @@
* Instead, the following rules determine the nest host of {@code C}:
* <ul>
* <li>If the nest host of the lookup class of this {@code Lookup} has previously
* been determined, then let {@code H} be the nest host of the lookup class.
* Otherwise, the nest host of the lookup class is determined using the
- * algorithm in JVMS 5.4.4, yielding {@code H}.</li>
+ * algorithm in JVMS {@jvms 5.4.4}, yielding {@code H}.</li>
* <li>The nest host of {@code C} is determined to be {@code H},
* the nest host of the lookup class.</li>
* </ul>
*
* <p> A hidden class or interface may be serializable, but this requires a custom
@@ -1874,11 +1927,11 @@
* @throws VerifyError if the newly created class cannot be verified
* @throws LinkageError if the newly created class cannot be linked for any other reason
* @throws NullPointerException if any parameter is {@code null}
*
* @since 15
- * @see Class#isHiddenClass()
+ * @see Class#isHidden()
* @jvms 4.2.1 Binary Class and Interface Names
* @jvms 4.2.2 Unqualified Names
* @jvms 4.7.28 The {@code NestHost} Attribute
* @jvms 4.7.29 The {@code NestMembers} Attribute
* @jvms 5.4.3.1 Class and Interface Resolution
@@ -1897,12 +1950,11 @@
ensureDefineClassPermission();
if (!hasFullPrivilegeAccess()) {
throw new IllegalAccessException(this + " does not have full privilege access");
}
- Set<ClassOption> opts = options.length > 0 ? Set.of(options) : Set.of();
- return makeHiddenClassDefiner(bytes.clone(), opts, false).defineClassAsLookup(initialize);
+ return makeHiddenClassDefiner(bytes.clone(), Set.of(options), false).defineClassAsLookup(initialize);
}
/**
* Creates a <em>hidden</em> class or interface from {@code bytes} with associated
* {@linkplain MethodHandles#classData(Lookup, String, Class) class data},
@@ -1940,11 +1992,11 @@
* @throws LinkageError if the newly created class cannot be linked for any other reason
* @throws NullPointerException if any parameter is {@code null}
*
* @since 15
* @see Lookup#defineHiddenClass(byte[], boolean, ClassOption...)
- * @see Class#isHiddenClass()
+ * @see Class#isHidden()
*/
/* package-private */ Lookup defineHiddenClassWithClassData(byte[] bytes, Object classData, ClassOption... options)
throws IllegalAccessException
{
Objects.requireNonNull(bytes);
@@ -1954,68 +2006,113 @@
ensureDefineClassPermission();
if (!hasFullPrivilegeAccess()) {
throw new IllegalAccessException(this + " does not have full privilege access");
}
- Set<ClassOption> opts = options.length > 0 ? Set.of(options) : Set.of();
- return makeHiddenClassDefiner(bytes.clone(), opts, false)
+ return makeHiddenClassDefiner(bytes.clone(), Set.of(options), false)
.defineClassAsLookup(true, classData);
}
- private ClassDefiner makeClassDefiner(byte[] bytes) {
- return new ClassDefiner(this, bytes, STRONG_LOADER_LINK);
+ /*
+ * Validates the given bytes to be a class or interface and the class name
+ * is in the same package as the lookup class.
+ *
+ * This method returns the class name.
+ */
+ private String validateAndGetClassName(byte[] bytes) {
+ try {
+ ClassReader reader = new ClassReader(bytes);
+ if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) {
+ throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
+ }
+ String name = reader.getClassName().replace('/', '.');
+ int index = name.lastIndexOf('.');
+ String pn = (index == -1) ? "" : name.substring(0, index);
+ if (!pn.equals(lookupClass.getPackageName())) {
+ throw newIllegalArgumentException(name + " not in same package as lookup class: " +
+ lookupClass.getName());
+ }
+ return name;
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ // ASM exceptions are poorly specified
+ ClassFormatError cfe = new ClassFormatError();
+ cfe.initCause(e);
+ throw cfe;
+ }
}
- /**
- * Returns a ClassDefiner that creates a {@code Class} object of a hidden class
+
+ /*
+ * Returns a ClassDefiner that creates a {@code Class} object of a normal class
* from the given bytes.
*
- * @param bytes class bytes
- * @return ClassDefiner that defines a hidden class of the given bytes.
+ * Caller should make a defensive copy of the arguments if needed
+ * before calling this factory method.
+ *
+ * @throws IllegalArgumentException if {@code bytes} is not a class or interface or
+ * {@bytes} denotes a class in a different package than the lookup class
*/
- ClassDefiner makeHiddenClassDefiner(byte[] bytes) {
- return makeHiddenClassDefiner(bytes, Set.of(), false);
+ private ClassDefiner makeClassDefiner(byte[] bytes) {
+ return new ClassDefiner(this, validateAndGetClassName(bytes), bytes, STRONG_LOADER_LINK);
}
/**
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
- * from the given bytes.
+ * from the given bytes. The name must be in the same package as the lookup class.
+ *
+ * Caller should make a defensive copy of the arguments if needed
+ * before calling this factory method.
*
- * @param name fully-qualified name that specifies the prefix of the hidden class
* @param bytes class bytes
* @return ClassDefiner that defines a hidden class of the given bytes.
+ *
+ * @throws IllegalArgumentException if {@code bytes} is not a class or interface or
+ * {@bytes} denotes a class in a different package than the lookup class
*/
- ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) {
- return makeHiddenClassDefiner(name, bytes, Set.of(), false);
+ ClassDefiner makeHiddenClassDefiner(byte[] bytes) {
+ return makeHiddenClassDefiner(validateAndGetClassName(bytes), bytes, Set.of(), false);
}
/**
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
- * from the given bytes and options. This method will read the class file
- * and obtain the class name.
+ * from the given bytes and options.
+ * The name must be in the same package as the lookup class.
+ *
+ * Caller should make a defensive copy of the arguments if needed
+ * before calling this factory method.
*
* @param bytes class bytes
* @param options class options
* @param accessVmAnnotations true to give the hidden class access to VM annotations
* @return ClassDefiner that defines a hidden class of the given bytes and options
+ *
+ * @throws IllegalArgumentException if {@code bytes} is not a class or interface or
+ * {@bytes} denotes a class in a different package than the lookup class
*/
ClassDefiner makeHiddenClassDefiner(byte[] bytes,
Set<ClassOption> options,
boolean accessVmAnnotations) {
- int flags = HIDDEN_CLASS | ClassOption.optionsToFlag(options);
- if (accessVmAnnotations | VM.isSystemDomainLoader(lookupClass.getClassLoader())) {
- // jdk.internal.vm.annotations are permitted for classes
- // defined to boot loader and platform loader
- flags |= ACCESS_VM_ANNOTATIONS;
+ return makeHiddenClassDefiner(validateAndGetClassName(bytes), bytes, options, accessVmAnnotations);
}
- return new ClassDefiner(this, bytes, flags);
+ /**
+ * Returns a ClassDefiner that creates a {@code Class} object of a hidden class
+ * from the given bytes. No package name check on the given name.
+ *
+ * @param name fully-qualified name that specifies the prefix of the hidden class
+ * @param bytes class bytes
+ * @return ClassDefiner that defines a hidden class of the given bytes.
+ */
+ ClassDefiner makeHiddenClassDefiner(String name, byte[] bytes) {
+ return makeHiddenClassDefiner(name, bytes, Set.of(), false);
}
/**
* Returns a ClassDefiner that creates a {@code Class} object of a hidden class
- * from the given bytes and options.
+ * from the given bytes and options. No package name check on the given name.
*
* @param name the name of the class and the name in the class bytes is ignored.
* @param bytes class bytes
* @param options class options
* @param accessVmAnnotations true to give the hidden class access to VM annotations
@@ -2038,29 +2135,10 @@
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, int flags) {
- // defining an ordinary class which must be a strongly referenced by its defining loader
- assert ((flags & HIDDEN_CLASS) != 0 || (flags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK);
- this.lookup = lookup;
- this.bytes = bytes;
- this.classFlags = flags;
- 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());
- }
- }
-
- // skip package name check
private ClassDefiner(Lookup lookup, String name, byte[] bytes, int flags) {
assert ((flags & HIDDEN_CLASS) != 0 || (flags & STRONG_LOADER_LINK) == STRONG_LOADER_LINK);
this.lookup = lookup;
this.bytes = bytes;
this.classFlags = flags;
@@ -2107,28 +2185,10 @@
}
private boolean isNestmate() {
return (classFlags & NESTMATE_CLASS) != 0;
}
-
- private static String className(byte[] bytes) {
- try {
- ClassReader reader = new ClassReader(bytes);
- if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) {
- throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set");
- }
- 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) {
@@ -3105,11 +3165,11 @@
private MethodHandle unreflectField(Field f, boolean isSetter) throws IllegalAccessException {
MemberName field = new MemberName(f, isSetter);
if (isSetter && field.isFinal()) {
if (field.isStatic()) {
throw field.makeAccessException("static final field has no write access", this);
- } else if (field.getDeclaringClass().isHiddenClass()){
+ } else if (field.getDeclaringClass().isHidden()){
throw field.makeAccessException("final field in a hidden class has no write access", this);
}
}
assert(isSetter
? MethodHandleNatives.refKindIsSetter(field.getReferenceKind())
@@ -3670,11 +3730,11 @@
throw getField.makeAccessException("caller class must be a subclass below the method", lookupClass());
}
refc = lookupClass();
}
return VarHandles.makeFieldHandle(getField, refc, getField.getFieldType(),
- this.allowedModes == TRUSTED && !getField.getDeclaringClass().isHiddenClass());
+ this.allowedModes == TRUSTED && !getField.getDeclaringClass().isHidden());
}
/** Check access and get the requested constructor. */
private MethodHandle getDirectConstructor(Class<?> refc, MemberName ctor) throws IllegalAccessException {
final boolean checkSecurity = true;
return getDirectConstructorCommon(refc, ctor, checkSecurity);
@@ -3782,65 +3842,10 @@
// oops
throw newIllegalArgumentException("bad MethodHandle constant #"+member);
}
static ConcurrentHashMap<MemberName, DirectMethodHandle> LOOKASIDE_TABLE = new ConcurrentHashMap<>();
-
- /**
- * The set of class options that specify whether a hidden class created by
- * {@link Lookup#defineHiddenClass(byte[], boolean, ClassOption...)
- * Lookup::defineHiddenClass} method is dynamically added as a new member
- * to the nest of a lookup class and/or whether a hidden class has
- * a strong relationship with the class loader marked as its defining loader.
- *
- * @since 15
- */
- public enum ClassOption {
- /**
- * This class option specifies the hidden class be added to
- * {@linkplain Class#getNestHost nest} of a lookup class as
- * a nestmate.
- *
- * <p> 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 to have a <em>strong</em>
- * relationship with the class loader marked as its defining loader,
- * as a normal class or interface has with its own defining loader.
- * This means that the hidden class may be unloaded if and only if
- * its defining loader is not reachable and thus may be reclaimed
- * by a garbage collector (JLS 12.7).
- *
- * <p> By default, a hidden class or interface may be unloaded
- * even if the class loader that is marked as its defining loader is
- * <a href="../ref/package.html#reachability">reachable</a>.
-
- *
- * @jls 12.7 Unloading of Classes and Interfaces
- */
- STRONG(STRONG_LOADER_LINK);
-
- /* 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<ClassOption> options) {
- int flags = 0;
- for (ClassOption cp : options) {
- flags |= cp.flag;
- }
- return flags;
- }
- }
}
/**
* Produces a method handle constructing arrays of a desired type,
* as if by the {@code anewarray} bytecode.
< prev index next >