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](https://bugs.openjdk.java.net/browse/JDK-8246775) 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](https://bugs.openjdk.java.net/browse/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.