Q: Why are $assertionsDisabled nested classes loaded eagerly by interface static initializers?
Eirik Bjørsnøs
eirbjo at gmail.com
Fri Jan 30 16:16:04 UTC 2026
On Thu, Jan 29, 2026 at 7:40 AM Eirik Bjørsnøs <eirbjo at gmail.com> wrote:
> Perhaps we could/should interpret this as constraints on the host system,
> rather than on any specific compiler implementation technique:
>
This sent me down a rabbit hole of javac homework and prototyping. Here's
some takeaways:
*ConstantDynamic is maybe not a great fit*
I prototyped using condy to resolve assertion status. Causes early
bootstrap issues and circularity issues for code using assert in anything
called from ConstantDynamic machinery. So probably not useful in java.base.
Benchmarking indicates performance is neutral vs current synthetic field.
And: JLS seems to tie assertion status to class initialization status, so
we need to track this state anyhow (so we are not really dealing with a
constant).
*Observation: Current javac implementation violates JLS*
The JSL states that *Whether a top level class or interface enables
assertions cannot be changed after it has been determined.*
The following code demonstrates how to change the assertion status of a
Nested class after its top level class has been initialized:
public interface AfterToplevelInit {
public static class Nested {
void m() {
assert false;
}
}
// Fails with AssertionError when run with -da
public static void main(String[] args) {
ClassLoader cl = AfterToplevelInit.class.getClassLoader();
cl.setClassAssertionStatus(AfterToplevelInit.class.getName(), true);
new Nested().m();
}
}
*Solution: Should the determined assertion status be tracked by
java.lang.Class?*
I prototyped adding a field Class::assertionStatus. Querying this status
will determine the assertion status of the class such that once queried,
the answer never changes.
Moving this state to Class helps us fix the above JLS violation (since in
contrast with synthetic fields, this state of the top level class will be
visible across all nestmates)
This solution also allows us to prevent the forced initialization of the
synthetic class during interface static initializers. We can instead
call determineAssertionStatus() on the top level class to "seal" the
determination of the assertion status. Saves us extra class loading when
using asserts in unused interface default/static methods.
My "exactly-once" (benign data race) initialization query method in
java.lang.Class looks like this:
public boolean determineAssertionStatus() {
byte status = this.assertionStatus;
if (status == 0) {
if (desiredAssertionStatus()) {
this.assertionStatus = 1;
return true;
} else {
this.assertionStatus = -1;
return false;
}
}
return status == 1;
}
Cheers,
Eirik.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/compiler-dev/attachments/20260130/b5321643/attachment-0001.htm>
More information about the compiler-dev
mailing list