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