JDK-8243582 : 5.3.5: Perform 'final' error checks during class loading, not verification
  • Type: Bug
  • Component: specification
  • Sub-Component: vm
  • Affected Version: 14
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2020-04-24
  • Updated: 2023-12-19
  • Resolved: 2020-12-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 16
16Fixed
Related Reports
Blocks :  
CSR :  
Relates :  
Relates :  
Description
In principle, it is wrong for a class to extend a final class or to declare a method that overrides a final method in a superclass.

JVM implementations have always respected this principle by refusing to load a class that attempts to do either of these things. Specifically, the longstanding behavior of HotSpot and J9 is to fail to define a class that either (i) extends a class whose ACC_FINAL flag is set, or (ii) declares a method that overrides a method whose ACC_FINAL flag is set. The failure occurs early enough in the class definition algorithm that no initiating loader is recorded for the given class name, and no `Class` object is exposed to user code.

Unfortunately, the JVMS has always specified these checks to occur after class loading, as part of verification (JVMS 4.10). It would be undesirable for a JVM implementation to adhere to the JVMS by waiting until verification, because it would violate expected invariants about loaded classes. For example, it would expose programmers to `Class` objects that represent classes which extend final classes.

(From a historical perspective, the discrepancy between the JVMS and JVM implementations is not surprising. The JVMS initially made no distinction between checking the format of a class file, verifying the code in a class file, and other class file validation actions -- they were all loosely considered "verification". There have since been multiple efforts to more precisely identify the timing and nature of such checks.)

Besides the timing of the ACC_FINAL checks, there is the question of which exception to throw if the checks fail. Checking a class and its methods against ACC_FINAL flags in superclasses is fundamentally about detecting inconsistency _between_ classes. Such inconsistencies are usually signaled with IncompatibleClassChangeError, such as when a class's named superclass turns out to be an interface. Unfortunately, JVM implementations have always thrown VerifyError when they detect an ACC_FINAL violation. This should be changed, reserving VerifyError for actual failures of verification.

Proposal: (1) Align the JVMS with JVM implementations by specifying that ACC_FINAL checks occur during class definition (JVMS 5.3.5), at the same time as other properties of the superclass are checked. (2) Specify that an IncompatibleClassChangeError occurs if the checks fail, modifying HotSpot to throw that instead of VerifyError (JDK-8243583).

This makes the JVMS 4.10 rules about ACC_FINAL redundant, because verification will never encounter a class that extends a final class. Cleaning up the verification spec is left as a separate task (JDK-8258138).
Comments
Additional changes to JVMS 5.3.5 to make the interface step consistent (and match longstanding HotSpot behavior): 4. If C has any direct superinterfaces, the symbolic references from C to its direct superinterfaces are resolved using the algorithm of ยง5.4.3.1. 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 any of the classes or interfaces named as direct superinterfaces of C is not in fact an interface, loading throws an IncompatibleClassChangeError.~~ - ~~Otherwise, if~~ **If** any of the superinterfaces of C is C itself, loading throws a ClassCircularityError. - **Otherwise, if any of the classes or interfaces named as direct superinterfaces of C is not in fact an interface, loading throws an IncompatibleClassChangeError.** (No further changes needed to JLS 13, which does not contemplate interface->class refactorings.)
12-12-2020

Proposed change to JVMS 5.3.5: 3. 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`.**
12-12-2020

The ACC_FINAL and ACC_INTERFACE checks should have the same timing, since they're a response to the same file contents (the flags of the superclass). And that most naturally occurs after the circularity check, since the superclass could be the (not-yet-loaded) current class, and queries about classes typically assume the class is loaded. Longstanding behavior of HotSpot is to give priority to the CCE (e.g., loading a class that names itself as a superinterface results in a CCE, not an ICCE).
12-12-2020

See also previous, overlapping changes proposed in the Sealed Classes JVM preview feature spec.
12-12-2020

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.
14-10-2020