JDK-8245871 : Define Lookup::ensureInitialized as a replacement API for Unsafe::ensureClassInitialized
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 15
  • Submitted: 2020-05-26
  • Updated: 2020-07-13
  • Resolved: 2020-06-06
Related Reports
CSR :  
Relates :  
Description
Summary
-------

Define `java.lang.invoke.MethodHandles.Lookup::ensureInitialized` as a replacement API for `sun.misc.Unsafe::ensureClassInitialized`

Problem
-------

Class initialization will happen as specified in JLS 12.4 and JVMS 5.5 that involves accessing its member.

An existing programmatic way to initialize a class is `Class::forName` but that requires to know the name of the class and a class loader that can find such class.   This may not always be feasible for frameworks.

Solution
--------

Add `Lookup::ensureInitialized` method to initialize the given class if the lookup has the access to the class to be initialized. 

`Class::ensureInitialized` was another option considered.   It should be caller sensitive in order to ensure the caller has the proper access to this class.  This alternative may not work for frameworks which have no access to user classes.  It would have to fallback to `Class::forName` limited solution. 

OTOH a framework can use `Lookup::privateLookupIn` to access a class in another module if the module authorizes it by opening the packages for it to access.   Or a user can produce a less privileged lookup by `Lookup::dropLookupMode` and pass it to the framework.

Specification
-------------

Add the `ensureInitialized` method in  `java.lang.invoke.MethodHandles.Lookup`:

```
        /**
         * Ensures that {@code targetClass} has been initialized. The class
         * to be initialized must be {@linkplain #accessClass accessible}
         * to this {@code Lookup} object.  This method causes {@code targetClass}
         * to be initialized if it has not been already initialized,
         * as specified in JVMS {@jvms 5.5}.
         *
         * @param targetClass the class to be initialized
         * @return {@code targetClass} that has been initialized
         *
         * @throws  IllegalArgumentException if {@code targetClass} is a primitive type or {@code void}
         *          or array class
         * @throws  IllegalAccessException if {@code targetClass} is not
         *          {@linkplain #accessClass accessible} to this lookup
         * @throws  ExceptionInInitializerError if the class initialization provoked
         *          by this method fails
         * @throws  SecurityException if a security manager is present and it
         *          <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
         * @since 15
         * @jvms 5.5 Initialization
         */
        public Class<?> ensureClassInitialized(Class<?> targetClass) throws IllegalAccessException {
```

Deprecate `sun.misc.Unsafe::ensureClassInitialized` and `sun.misc.Unsafe::shouldBeInitialized` method for removal.

````
@@ -710,7 +710,11 @@
      * Ensures the given class has been initialized. This is often
      * needed in conjunction with obtaining the static field base of a
      * class.
+     *
+     * @deprecated Use the {@link java.lang.invoke.MethodHandles.Lookup#ensureInitialized(Class)}
+     *             method instead.
      */
+    @Deprecated(since = "15", forRemoval = true)
     @ForceInline
     public void ensureClassInitialized(Class<?> c) {
         theInternalUnsafe.ensureClassInitialized(c);
```

```
+     *
+     * @deprecated No replacement API for this method.  As multiple threads
+     * may be trying to initialize the same class or interface at the same time.
+     * The only reliable result returned by this method is {@code false}
+     * indicating that the given class has been initialized.  Instead, simply
+     * call {@link java.lang.invoke.MethodHandles.Lookup#ensureInitialized(Class)}
+     * that does nothing if the given class has already been initialized.
+     * This method is subject to removal in a future version of JDK.
+     *
      * @return false only if a call to {@code ensureClassInitialized} would have no effect
+     *
      */
+    @Deprecated(since = "15", forRemoval = true)
     @ForceInline
     public boolean shouldBeInitialized(Class<?> c) {

```
Comments
Hidden classes are just normal classes from JVM's perspective and w.r.t. Lookup API (reflective access). I prefer to keep the spec as is and not to highlight hidden classes as it's not any special than normal classes. W.r.t. to primitive type, void and array type, primitive types, void and array types don't have `<clinit>`. `Lookup::in` is a precedence that throws IAE if the target class is a primitive type, void and array types. I'd suggest to have this API throw IAE. If we get feedback in this particular usage, we can consider changing it to pass through in the future.
08-06-2020

The specification of the new method includes text covering some of the usual special cases for Class objects, primitive, void, and arrays. (As an aside, was there consideration of just letting these values pass through rather than throw an exception?) Hidden classes are a new category of special case. As a code review comment, I suggest adding text explicitly stating hidden classes are handled just like non-hidden classes by the method.
08-06-2020

`Lookup::ensureInitialized` can be used to initialize a class or interface -- normal and hidden class -- as long as the Lookup object has access to the class to be initialized. No need for special mentioning of hidden classes in this API.
08-06-2020

Should ensureClassInitialized discuss hidden classes (JDK-8220607)? Moving to Approved. Please update and re-finalize if an update should be done for hidden classes.
06-06-2020

This works well on Lookup rather than java.lang.Class.
04-06-2020