Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8238359

Implementation of JEP 371: Hidden Classes

    XMLWordPrintable

    Details

    • Subcomponent:
    • Compatibility Risk:
      low
    • Compatibility Risk Description:
      Hide
      1. `Class::getName` traditionally returns a binary name, but for a hidden class it returns a string that contains an ASCII forward slash (`/`) and is therefore not a binary name. Programs that assume the returned string is a binary name may need to be updated to handle hidden classes. That said, the longstanding practice of `Unsafe::defineAnonymousClass` was to define classes whose names were not binary names, so some programs may already handle such names successfully.

      2. `Class::getNestMembers` is changed to not throw an exception when it fails to validate a nest membership of any member listed in `NestMembers` attribute. Instead, `Class::getNestMembers` returns the nest host plus the members listed in the host's `NestMembers` attribute that are successfully resolved and determined to have the same nest host as this class. (This means it may return fewer members that listed in `NestMembers` attribute.) Existing code that expects `LinkageError` if there is a bad nest membership may be impacted. However, this method is typically used for troubleshooting and the compatibility risk is expected to be low.
       
      3. The nestmate test in the JVM is changed to throw only `IllegalAccessError` when the nest membership is invalid. Some historical understanding is necessary:
      - In Java 8, every access control failure was signaled with IllegalAccessError (IAE). Moreover, if a given access check failed with IAE once, then the same check would fail with IAE every time.
      - In Java 11, the introduction of nest mates (JEP 181) meant that an access control failure could be signaled either with IllegalAccessError or, if nest membership was invalid, LinkageError. Still, if a given access check failed with a specific exception, then the same check would always fail with the same exception.
      - In Java 15, the introduction of `Lookup::defineHiddenClass` implies that the nest host of the lookup class must be determined eagerly, when the hidden class is defined as a nestmate of the lookup class. Both `Lookup::defineHiddenClass` and `Class::getNestHost` both determine the nest host of a class in a more resilient manner than the JVM did in Java 11; namely, the API simply treats a class as self-hosted if its purported nest membership is invalid. For consistency with the API, the JVM no longer throws LinkageError when a class's nest membership is invalid, and instead treats the class as self-hosted. This means that the JVM only ever throws IAE from access control (because a self-hosted class will not permit any other class to access its private members) -- this is the behavior expected by the vast majority of user code.

      4. JVM TI agents may need updating for hidden classes if they assume GetClassSignature returns a valid type descriptor. JDI implementation handles hidden classes. In addition hidden classes are not modifiable. It is believed that the impact to existing JVM TI agents should be limited.

      5. `Class::descriptorString` and `MethodType::descriptorString` is changed to return a string that contains `.` character which is illegal in a type descriptor specified in JVMS 4.3 if the runtime entity cannot be described nominally. Existing libraries that assume the returned descriptor string is always a valid type descriptor needs updating. The risk of this behavioral change is believed to be low since `java.lang.constant` API is relatively new (added in Java SE 12) target for bytecode reading/writing tools and libraries.
      Show
      1. `Class::getName` traditionally returns a binary name, but for a hidden class it returns a string that contains an ASCII forward slash (`/`) and is therefore not a binary name. Programs that assume the returned string is a binary name may need to be updated to handle hidden classes. That said, the longstanding practice of `Unsafe::defineAnonymousClass` was to define classes whose names were not binary names, so some programs may already handle such names successfully. 2. `Class::getNestMembers` is changed to not throw an exception when it fails to validate a nest membership of any member listed in `NestMembers` attribute. Instead, `Class::getNestMembers` returns the nest host plus the members listed in the host's `NestMembers` attribute that are successfully resolved and determined to have the same nest host as this class. (This means it may return fewer members that listed in `NestMembers` attribute.) Existing code that expects `LinkageError` if there is a bad nest membership may be impacted. However, this method is typically used for troubleshooting and the compatibility risk is expected to be low.   3. The nestmate test in the JVM is changed to throw only `IllegalAccessError` when the nest membership is invalid. Some historical understanding is necessary: - In Java 8, every access control failure was signaled with IllegalAccessError (IAE). Moreover, if a given access check failed with IAE once, then the same check would fail with IAE every time. - In Java 11, the introduction of nest mates (JEP 181) meant that an access control failure could be signaled either with IllegalAccessError or, if nest membership was invalid, LinkageError. Still, if a given access check failed with a specific exception, then the same check would always fail with the same exception. - In Java 15, the introduction of `Lookup::defineHiddenClass` implies that the nest host of the lookup class must be determined eagerly, when the hidden class is defined as a nestmate of the lookup class. Both `Lookup::defineHiddenClass` and `Class::getNestHost` both determine the nest host of a class in a more resilient manner than the JVM did in Java 11; namely, the API simply treats a class as self-hosted if its purported nest membership is invalid. For consistency with the API, the JVM no longer throws LinkageError when a class's nest membership is invalid, and instead treats the class as self-hosted. This means that the JVM only ever throws IAE from access control (because a self-hosted class will not permit any other class to access its private members) -- this is the behavior expected by the vast majority of user code. 4. JVM TI agents may need updating for hidden classes if they assume GetClassSignature returns a valid type descriptor. JDI implementation handles hidden classes. In addition hidden classes are not modifiable. It is believed that the impact to existing JVM TI agents should be limited. 5. `Class::descriptorString` and `MethodType::descriptorString` is changed to return a string that contains `.` character which is illegal in a type descriptor specified in JVMS 4.3 if the runtime entity cannot be described nominally. Existing libraries that assume the returned descriptor string is always a valid type descriptor needs updating. The risk of this behavioral change is believed to be low since `java.lang.constant` API is relatively new (added in Java SE 12) target for bytecode reading/writing tools and libraries.
    • Interface Kind:
      Java API
    • Scope:
      SE

      Description

      Summary

      Introduce hidden classes, which are classes that cannot be used directly by the bytecode of other classes. Hidden classes are intended for use by frameworks that generate classes at run time and use them indirectly, via reflection. A hidden class may be defined as a member of a nest for access control purposes, and may be unloaded while its defining loader is reachable.

      Problem

      The method sun.misc.Unsafe::defineAnonymousClass is a powerful internal API used by language implementions, such as for those for lambda expressions and Nashorn bindings. However, the VM-anonymous classes defined by this method are ill-defined and require special-case access checks. It is time to provide a standard mechanism to replace VM-anonymous classes.

      The goals of JEP 371:

      • Allow frameworks to define classes as non-discoverable implementation details of the framework, so that they cannot be linked against by other classes nor discovered through reflection.
      • Support extending an access-control nest with non-discoverable classes.
      • Support aggressive unloading of non-discoverable classes, so that frameworks have the flexibility to define as many as they need.
      • Deprecate the non-standard API sun.misc.Unsafe::defineAnonymousClass, with the intent to deprecate it for removal in a future release.
      • Do not change the Java programming language in any way.

      Solution

      This JEP proposes to extend the Lookup API to support defining a hidden class that can only be accessed by reflection. A hidden class is not discoverable by the JVM during bytecode linkage, nor by programs making explicit use of class loaders (via, e.g., Class::forName and ClassLoader::loadClass). Optionally, a hidden class can be created as a member of an access control nest, and a hidden class can have the same strong relationship with its defining loader as a normal class has with its own defining loader.

      This CSR proposes to add a new method Lookup::defineHiddenClass for creating a hidden class. The API specification specifies the algorithm of the creation of a hidden class. The JVM itself never creates a hidden class, and hidden class creation has no impact on the JVM Specification.

      Lookup::defineHiddenClass takes an option ClassOption::NESTMATE to specify if the newly created hidden class is added as a new member to an existing nest, and an option ClassOption::STRONG to specify if the newly created hidden class has the same strong relationship with its defining class loader as the normal class has with its own defining loader (this guarantees that the hidden class may be unloaded if and only if its defining class loader is reclaimed).

      The dynamic nest membership enjoyed by hidden classes requires an update to the JVM Specification's rules for access control. Hidden classes cannot participate in the nest membership defined by NestHost and NestMembers attributes because they do not have binary names that can be expressed, in internal form, in those attributes. However, Lookup::defineHiddenClass can arrange for a hidden class to be a member of the same nest as the lookup class (Lookup::lookupClass) This means that, from the JVM's point of view, a class's nest membership may have been determined prior to access control; in that case, we wish for the JVM to use that membership during access control, and more specifically during the "nestmate test" (JVMS 5.4.4).

      Separately, we wish to reduce the failure modes of access control in connection with nest membership. The nestmate test introduced in Java 11 is brittle because any failure to determine a nest host results in the JVM throwing a LinkageError other than an IllegalAccessError. This behavior is surprising, and inconsistent with how both Lookup::defineHiddenClass and Class::getNestHost behave when they experience problems trying to determine a nest host. Accordingly, if the JVM attempts to determine a nest host for a class, but various ancillary exceptions occur, then the JVM should swallow them, and determine that the class is its own nest host. In this way, and like the API, the JVM never fails to determine a nest host. The nestmate test may then succeed or fail in the normal way, based on the nests determined for the accessor and accessee.

      Specification

      Attached jep-371-specdiff-v3.zip and Draft-JVMS-HiddenClasses.pdf are the specdiff and JVMS change for (1) to (9) below. jep-371-specdiff-inc.zip for (10) to (15).

      API javadoc/specdiff for reference: http://cr.openjdk.java.net/~mchung/valhalla/webrevs/hidden-classes/api/java.base/module-summary.html http://cr.openjdk.java.net/~mchung/valhalla/webrevs/hidden-classes/specdiff/

      Summary of API changes:

      1) java.lang.invoke.MethodHandles.Lookup::defineHiddenClass(byte[] bytes, boolean initialize, ClassOption... options)

      This is the API to create a hidden class from the given bytes. The JVM never creates hidden classes itself. The API specification specifies the algorithm for the creation of a hidden class. Hidden class creation has no impact to the JVMS.

      The support for a hidden class to be added to an existing nest has impact on JVMS section 5.4.4 "access control" and nest host determination (see below).

      2) New enum class java.lang.invoke.MethodHandles.Lookup.ClassOption

      NESTMATE and STRONG options allow a hidden class to optionally be a nestmate of the lookup class and have the same strong relationship with its defining loader as a normal class has with its own defining loader.

      By default, a hidden class belongs to the nest of itself and it may be unloaded while its defining class loader is still alive.

      3) New Class::isHidden method

      4) Class::getName of a hidden class will return an invalid binary name containing / character (see JVMS change). Tools and applications that rely on the returned name being a valid binary name will need to be updated to handle hidden classes properly.

      5) Class::getNestMembers is changed to not throw an exception if it fails to validate the nest membership of any member listed in the NestMembers attribute. Instead, Class::getNestMembers returns the nest host plus the members listed in the NestMembers attributes that were successfully resolved and determined to have the same nest host as this class. So it may return fewer members than listed in the NestMembers attribute.

      6) java.lang.reflect.Field::set and other setX methods throw IllegalAccessError if the field is final and its declaring class is a hidden class.

      7) Deprecate sun.misc.Unsafe::defineAnonymousClass API with removal=false.

      +     *
      +     * @deprecated Use the {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...)}
      +     * method.
      +     *
            * @param hostClass context for linkage, access control, protection domain, and class loader
            * @param data      bytes of a class file
            * @param cpPatches where non-null entries exist, they replace corresponding CP entries in data
            */
           @ForceInline
      +    @Deprecated(since = "15", forRemoval = false)
           public Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches) {
               return theInternalUnsafe.defineAnonymousClass(hostClass, data, cpPatches);
           }

      8) JVM TI GetClassSignature returns a JNI-style signature and it returns a name for a hidden class containing ".".

      9) JVMS

      JDK-8231313 has the proposed JVMS 5.4.4 change to enhance access control for dynamically-determined nest membership. The draft JVMS change is cut-n-paste below. Attached Draft-JVMS-HiddenClasses.pdf is the better formatted version.

      The second half of 5.4.4 is modified as follows, using * to indicate addition, and ~~~ to indication deletion.


      If R is not accessible to D, then access control throws an IllegalAccessError.

      ~~~If R is public, protected, or has default access, then access control throws an IllegalAccessError.~~~ ~~~If R is private, then the nestmate test failed, and access control fails for the same reason.~~~~

      Otherwise, access control succeeds.

      A nest is a set of classes and interfaces that allow mutual access to their private members. One of the classes or interfaces is the nest host. It enumerates the classes and interfaces which belong to the nest, using the NestMembers attribute (§4.7.29). Each of them in turn designates it as the nest host, using the NestHost attribute (§4.7.28). A class or interface which lacks a NestHost attribute belongs to the nest hosted by itself; if it also lacks a NestMembers attribute, this nest is a singleton consisting only of the class or interface itself. The nest host for a given class or interface (that is, the nest to which the class or interface belongs) is determined by the Java Virtual Machine as part of access control, rather than when the class or interface is loaded. Certain methods of the Java SE Platform API may determine the nest host for a given class or interface prior to access control, in which case the Java Virtual Machine respects the prior determination during access control.

      To determine whether a class or interface C belongs to the same nest as a class or interface D, the nestmate test is applied. C and D belong to the same nest if and only if the nestmate test succeeds. The nestmate test is as follows:

      • If C and D are the same class or interface, then the nestmate test succeeds.

      • Otherwise, the following steps are performed, in order:

        1. ~~~The nest host of D, H, is determined (below). If an exception is thrown, then the nestmate test fails for the same reason.~~~ Let H be the nest host of D, if the nest host of D has previously been determined. If the nest host of D has not previously been determined, then it is determined using the algorithm below, yielding H.

        2. ~~~The nest host of C, H', is determined (below). If an exception is thrown, then the nestmate test fails for the same reason.~~~ Let H' be the nest host of C, if the nest host of C has previously been determined. If the nest host of C has not previously been determined, then it is determined using the algorithm below, yielding H'.

        3. H and H' are compared. If H and H' are the same class or interface, then the nestmate test succeeds. Otherwise, the nestmate test fails. ~~~by throwing an IllegalAccessError.~~~

      The nest host of a class or interface M is determined as follows:

      • If M lacks a NestHost attribute, then M is its own nest host.

      • Otherwise, M has a NestHost attribute, and its host_class_index item is used as an index into the run-time constant pool of M. The symbolic reference at that index is resolved to a class or interface H (§5.4.3.1). Then:

        ~~~During resolution of this symbolic reference, any of the exceptions pertaining to class or interface resolution can be thrown. Otherwise, resolution of H succeeds.~~~ If resolution of this symbolic reference fails, then M is its own nest host. Any exception thrown as a result of failure of class or interface resolution is not rethrown.

        ~~~If any of the following is true, an IncompatibleClassChangeError is thrown:~~~ Otherwise, if resolution succeeds but any of the following is true, then M is its own nest host:

        • H is not in the same run-time package as M.
        • H lacks a NestMembers attribute.
        • H has a NestMembers attribute, but there is no entry in its classes array that refers to a class or interface with the name N, where N is the name of M.

        Otherwise, H is the nest host of M.


      Attached jep-371-specdiff-inc.zip shows the spec change for the following items:

      This spec update is regarding the new form of a descriptor string for a hidden class or interface:

          "L" + N + "." + <suffix> + ";"

      where N is the binary name encoded in the internal form of the bytes passed to Lookup::defineHiddenClass.

      10) java.lang.invoke.TypeDescriptor and java.lang.invoke.TypeDescriptor.OfField and java.lang.invoke.TypeDescriptor.OfMethod spec are changed to specify that it can represent runtime entities that cannot be described nominally. TypeDescriptor::descriptorString returns a string that does not conform to JVMS 4.3 if this entity cannot be described nominally.

      11) Class::descriptorString returns a string which is not a valid type descriptor if this Class object represents a hidden class or interface or represents an array class whose element type is a hidden class or interface. Such class or interface cannot be described nominally.

      This is a behaviorlal incompatible change that may impact existing libraries that pass the resulting string to an API that requires it be a valid type descriptor. Existing code using the java.lang.constant API needs update to prepare hidden classes regardless of this change.

      Class::describeConstable returns an empty optional for a Class object that cannot be described nominally.

      12) Similarly java.lang.invoke.MethodType::descriptorString and MethodType::toMethodDescriptorString returns a string which is not a valid method descriptor if this method type cannot be described nominally. MethodType::describeConstable returns an empty optional.

      13) com.sun.jdi.Type::signature and com.sun.jdi.event.ClassUnloadEvent::classSignature and JDWP corresponding commands are updated to return the string returned by Class::descriptorString and it includes . character which is illegal in a type descriptor.

      Debugger tools need update to support hidden classes.

      14) Type::name, com.sun.jdi.ReferenceType::name and com.sun.jdi.event.ClassUnloadEvent::className and JDWP corresponding commands are updated to return the string returned by Class::getName that may not be a binary name.

      This has been a long standing behavior due to VM anonymous classes. This is not new to debuggers.

      15) Spec clarification in JVM TI, JDWP, JDI and java.lang.instrument.Instrumentation in (a) APIs to return loaded classes include classes created by class loaders as well as Java SE APIs such as hidden classes (b) APIs to return classes who can be found by a class loader (aka initiating class loader) that do not include hidden classes.

      The javadoc of the following APIs are updated and no behavior change:

      - JVM TI `GetLoadedClasses`
      - JVM TI  `GetClassLoaderReference`
      - JVM TI `ClassLoad` event
      - `java.lang.instrument.Instrumentation::getAllLoadedClasses`
      - `java.lang.instrument.Instrumentation::getInitiatedClasses
      - `com.sun.jdi.ClassLoaderReference::definedClasses`
      - `com.sun.jdi.ClassLoaderReference::visibleClasses`

        Attachments

        1. jep-371-specdiff-v3.zip
          2.08 MB
        2. jep-371-specdiff-v2.zip
          2.08 MB
        3. jep-371-specdiff-inc.zip
          3.17 MB
        4. jep-371-specdiff.zip
          2.04 MB
        5. Draft-JVMS-JEP-371.pdf
          14 kB
        6. Draft-JVMS-HiddenClasses.pdf
          6 kB

          Issue Links

            Activity

              People

              Assignee:
              mchung Mandy Chung
              Reporter:
              mchung Mandy Chung
              Reviewed By:
              Alan Bateman, Alex Buckley, Chris Plummer, David Holmes, John Rose, Serguei Spitsyn
              Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: