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

Change 'final' error checks to throw ICCE

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P4 P4
    • 16
    • hotspot
    • None
    • behavioral
    • low
    • The `LinkageError` thrown when loading a class that violates `final` restrictions changes from `VerifyError` to `IncompatibleClassChangeError`. This is a behavior change affecting programs that catch `LinkageErrors` and expect to see particular subtypes.
    • Class file construct
    • SE

      Summary

      Change the exception thrown by the JVM when a class being loaded extends a final class or overrides a final instance method from VerifyError to IncompatibleClassChangeError.

      Problem

      The enforcement of ACC_FINAL flags on classes and methods has long been specified as part of verification. Accordingly, JVM implementations throw VerifyError when a class extends a final class or overrides a final instance method.

      However, the enforcement of ACC_FINAL flags has nothing to do with verification. JVM implementations perform these checks on a class when it is being derived (part of class loading, prior to verification). This makes sense, because it is best not to permit classes to be loaded if they violate the basic subclassing constraints of another class.

      It is incongruous for the JVM Spec to enforce ACC_FINAL flags as part of verification when JVM implementations enforce them as part of derivation.

      In the future, sealed classes will need these details to be ironed out so that their checks can behave consistently.

      Solution

      Update Hotspot to throw an IncompatibleClassChangeError rather than a VerifyError.

      Update JVMS to specify the check when a class is derived in 5.3.5, throwing an IncompatibleClassChangeError.

      Specification details:

      If a superclass has its ACC_INTERFACE flag set, class loading throws an IncompatibleClassChangeError. The ACC_FINAL check is similar, and so it makes sense to throw the same error.

      The timing of this check is tricky, in relation to the ClassCircularityError check. Interfaces are required (as a format check) to extend java/lang/Object, so the relative timing is irrelevant in that case. For the final check, it makes more sense to check a class's attributes after the class has been successfully loaded (which can't happen if there's a circularity error). This is what HotSpot does: the circularity error has precedence over the final superclass class error.

      Compatibility discussion:

      Most programs do not attempt to catch and handle LinkageErrors. Among those that do, typical behavior would be to attempt to load an arbitrary class and then respond to all forms of errors that might arise, including VerifyErrors and IncompatibleClassChangeErrors. Those programs will still work properly.

      The biggest risk is for a theoretical program that attempts to load a class knowing that its superclass might be final, and also knowing that its superclass will never be an interface. Its unlikely that this scenario would arise in real-world code.

      Specification

      As described in JDK-8243582, changes to JVMS 5.3.5 (interpret "~~" as "strikeout" indicators, and "**" as insertion indicators):

      If *C* has a direct superclass, the symbolic reference from *C* to its 
      direct superclass is resolved using the algorithm of [5.4.3.1]. 
      Note that if *C* is an interface it must have `Object` as its direct 
      superclass, which must already have been loaded. 
      Only `Object` has no direct superclass. 
      
      Any exceptions that can be thrown due to class or interface resolution can 
      be thrown as a result of this phase of loading. 
      In addition, this phase of loading must detect the following errors: 
      
      - ~~If the class or interface named as the direct superclass of *C* is in 
          fact an interface, loading throws an `IncompatibleClassChangeError`.~~ 
      
      - ~~Otherwise, if~~ **If** any of the superclasses of *C* is *C* itself, 
          loading throws a `ClassCircularityError`. 
      
      - **Otherwise, if the class or interface named as the direct superclass of 
          *C* is an interface or a `final` class, derivation throws a 
          `IncompatibleClassChangeError`.** 
      
      - **Otherwise, if *C* is a class and some non-`static` method declared in 
          *C* can override ([5.4.5]) a `final`, non-`static` method declared in a 
          superclass of *C*, derivation throws an `IncompatibleClassChangeError`.** 

      Also update JLS 13.4.2:

      If a class that was not declared final is changed to be declared final, then ~~a VerifyError~~ an IncompatibleClassChangeError is thrown if a binary of a pre-existing subclass of this class is loaded, because final classes can have no subclasses; such a change is not recommended for widely distributed classes.

      And JLS 13.4.17:

      If Super is recompiled but not Test, then running the new binary with the existing binary of Test results in ~~a VerifyError~~ an IncompatibleClassChangeError because the class Test improperly tries to override the instance method out.

            dlsmith Dan Smith
            dlsmith Dan Smith
            Harold Seigel (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: