-
CSR
-
Resolution: Approved
-
P4
-
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 LinkageError
s. 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 VerifyError
s and IncompatibleClassChangeError
s. 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.
- csr of
-
JDK-8243582 5.3.5: Perform 'final' error checks during class loading, not verification
- Resolved
-
JDK-8243583 Change 'final' error checks to throw ICCE
- Resolved