JDK-8222071 : Class.forName may return a reference to a loaded but not linked Class
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang:class_loading
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 14
  • Submitted: 2019-04-05
  • Updated: 2019-10-30
  • Resolved: 2019-09-07
Related Reports
CSR :  
Relates :  
Description
Summary
-------
Two variants of the `Class.forName()` method: 

 - `Class.forName(String,boolean,ClassLoader)` (using `false` for `initialize`)
 - `Class.forName(Module,String)`

are specified to locate, load, and link a Class,
but may return classes that have not been linked.

Problem
-------

The 3-arg `Class.forName(String,boolean,ClassLoader)` has a boolean argument to specify whether or not to initialize the loaded class.  If true, the class is linked, and then initialized.  If false, the Class should still be linked, per the spec ("*this method attempts to locate, load, and link the class or interface*").  But as it is, the Class does not get linked until it is used.

This is not a problem for most usages, but can be for debugging/servicability/etc.  ([JDK-8181144][1], for example - a class loaded and unused in this way will not show up in `VirtualMachine.allClasses()`.)

Conforming to the spec and linking classes in Class.forName() would change long-standing behavior.  Various `LinkageError` subtypes can be thrown at class link time.  Linking during Class.forName() could change such exceptions to be thrown earlier.  Consider a Class loaded (and not initialized) by forName(), but *never* used.  There could be latent linkage errors which have never been encountered.  With this change in behavior, LinkageError would now be thrown from Class.forName().

Solution
--------

The behavior of the following methods will be updated to conform to the spec, linking the class before returning:

 - `Class.forName(String,boolean,ClassLoader)` (when `initialize` is `false`)
 - `Class.forName(Module,String)`
 - `MethodHandles.Lookup.findClass(String)`

Lookup.findClass() and the 2-arg Class.forName() were added in JDK 9, so they're unlikely to be widely used.

The 3-arg Class.forName() is already specified to throw LinkageError (all 3 above methods are), so developers **should** expect that it could occur.  However such checking is not required by the language (unchecked Error).  A <code>-XX:+ClassForNameDeferLinking</code> flag will be added to restore the previous behavior.

The spec for MethodHandles.Lookup.findClass(String) will be updated to mention that it loads *and links* the class.

An alternative solution that was considered would be to leave the implementation as is, update the spec to match the current behavior of not linking the class, and add a new, 4-arg Class.forName() variant with two boolean arguments - one to request linking (or not), and one to request initialization (or not).

Changing the behavior is preferred to adding a likely rarely-used method, given that the compatibility risk is believed to be manageable.

Specification
-------------
`java.lang.invoke.MethodHandles.Lookup`

in src/java.base/share/classes/java/lang/invoke/MethodHandles.java:

    @@ -1927,12 +1927,17 @@
                 /**
        -         * Looks up a class by name from the lookup context defined by this {@code Lookup} object. The static
        +         * Looks up a class by name from the lookup context defined by this {@code Lookup} object.
        +         * This method attempts to locate, load, and link the class, and then determines whether
        +         * the class is accessible to this {@code Lookup} object.  The static
                  * initializer of the class is not run.
                  * <p>
                  * The lookup context here is determined by the {@linkplain #lookupClass() lookup class}, its class
        -         * loader, and the {@linkplain #lookupModes() lookup modes}. In particular, the method first attempts to
        -         * load the requested class, and then determines whether the class is accessible to this lookup object.
        +         * loader, and the {@linkplain #lookupModes() lookup modes}.
        +         * <p>
        +         * Note that this method throws errors related to loading and linking as
        +         * specified in Sections 12.2 and 12.3 of <em>The Java Language
        +         * Specification</em>.
                       *
    @@ -1944,6 +1949,9 @@
    +         *
    +         * @jls 12.2 Loading of Classes and Interfaces
    +         * @jls 12.3 Linking of Classes and Interfaces
              * @since 9
              */
             public Class<?> findClass(String targetName) throws ClassNotFoundException, IllegalAccessException

`java.lang.Class`

in src/java.base/share/classes/java/lang/Class.java:

    @@ -390,10 +390,14 @@
    +     *
    +     * @jls 12.2 Loading of Classes and Interfaces
    +     * @jls 12.3 Linking of Classes and Interfaces
    +     * @jls 12.4 Initialization of Classes and Interfaces
          * @since     1.2
          */
         @CallerSensitive
         public static Class<?> forName(String name, boolean initialize,
                                        ClassLoader loader)
        ...
        @@ -436,10 +440,14 @@
              * accessible to its caller. </p>
              *
        +     * <p> Note that this method throws errors related to loading and linking as
        +     * specified in Sections 12.2 and 12.3 of <em>The Java Language
        +     * Specification</em>.
        +     * 
              * @apiNote
              * This method returns {@code null} on failure rather than
        @@ -463,10 +471,12 @@
              *         in a module.</li>
              *         </ul>
              *
        +     * @jls 12.2 Loading of Classes and Interfaces
        +     * @jls 12.3 Linking of Classes and Interfaces
              * @since 9
              * @spec JPMS
              */
             @CallerSensitive
             public static Class<?> forName(Module module, String name)



A new JDK-specific VM product flag is added for backward compatibility:

Setting <code>-XX:+ClassForNameDeferLinking</code> will restore the previous behavior, where a class is not necessarily linked by `Class.forName()`, but when the class is first used.


  [1]: https://bugs.openjdk.java.net/browse/JDK-8181144 "JDK-8181144"
Comments
I see the parent bug is already marked to get a release note; moving to Approved.
07-09-2019

"Note that this method ..." feels like an apiNote but this is established wording in the Class.forName API docs so I think it's okay.
06-09-2019

Thanks for the clarification that ACE is an anomaly. I have no further comments on the specification aspect of this. Adding myself as a reviewer.
06-09-2019

May as well as well add jls tags in Class.forName, too.
05-09-2019

To quote Alan: > "The other thing is that XX have good lifecycle support we can eventually deprecate/remove this temporary workaround - we've nothing like this for properties."
05-09-2019

Yes, use of a system property was considered (and, in fact, prototyped) during development. It was decided that a VM flag was a better approach overall.
05-09-2019

Moving to Provisional, please add reviewers before finalizing. Was there a discussion if a system property or command line option was preferable to controlling reverting to the old behavior?
05-09-2019

AccessControlException thrown during verification was an unfortunate long-standing undocumented behavior (private VM call out to `ClassLoader::checkPackageAccess`). It is planned to remove that private `ClassLoader::checkPackageAccess` call when the default for `--illegal-access` is flipped to deny (JDK-8218203)
05-09-2019

A couple of comments. First, LinkageErrors are not the only exceptions that may be thrown at link-time - for example IllegalAccessError can be thrown during verification. And there may be others. This somewhat weakens "they already specify to throw LinkageError" argument, but not sufficient in my opinion to warrant any change to what is proposed. Arguably it is a flaw in the JVMS/JLS that linking can throw exceptions beyond LinkageErrors and the always possible VirtualMachineErrors. Second, a minor nit, that as the MethodHandles method already has `@throws LinkageError if the linkage fails`, the extra note is not really necessary. If deemed necessary I'm not sure it conforms to the preferred way to reference the JLS or JVMS.
05-09-2019

Sorry confused IllegalAccessError with IllegalAccessException. So the only non-LinkageError is AccessControlException?
05-09-2019

IllegalAccessError isa LinkageError (by way of IncompatibleClassChangeError), so I would think that it is covered when mentioning LinkageError. Am I missing something? The new JLS reference in MethodHandles.Lookup.findClass() matches how the JLS is referenced in the 3-arg Class.forName() (which it calls).
05-09-2019

Tweaked the line: A new JDK-specific VM product flag is added for backward compatibility:
05-09-2019