Field initialization before 'super'
Dan Smith
daniel.smith at oracle.com
Fri Jan 26 21:48:18 UTC 2024
Having worked through the JLS changes (which I'll be sharing at some point soon), here are a few extra details that I think make the most sense:
> On Dec 12, 2023, at 4:27 PM, Dan Smith <daniel.smith at oracle.com> wrote:
>
> To enable and take advantage of early field initialization, we've envisioned the following changes:
>
> 1) As an exception to the general rule about 'this' usage, a "pre-construction context" allows writes to blank instance fields of the class. (The terminology may need updating, since you're clearly "constructing" the object if you're writing to its fields.) The fields are "write-only" at this stage—you can write into them but can't read them back.
Terminology: maybe "early construction context" rather than "pre-construction context"?
Enabling the capability to write to *blank* instance fields, but not fields with instance initializers, sidesteps any confusion about the timing of initializer execution, while still giving programmers the capability to prevent any unwanted reads of a field's default (null/zero) value, whether the field is final or not. (Would it be nice if fields with initializers could also, in some cases, be initialized early? Yes, but it's an incompatible change, so more work needed to navigate that problem.)
> At a 'this()' call, all final fields must be DU (because the delegated constructor will perform its own writes). No such restriction is needed for non-final fields; but it's an open question whether we should prohibit all writes before 'this()' anyway.
In the interest of not making arbitrary language rules, I prefer not to special-case the early construction context of a 'this'-calling constructor, other than introducing the DU rule for final fields.
> Writes to non-final fields with initializers are disallowed, to avoid confusion about sequencing (the field initializer will always run later, overwriting whatever you put in the constructor prologue.)
Yeah, this was the main concern about assignments before 'this()' calls. If you can't write to fields with initializers, then the timing of writes to a mutable field should be clear from the code. Whether it's a good way to structure a program is a stylistic choice. <shrug>
---
And some comments about other proposed features:
> 2) If a final field is written before 'super()' via every constructor in the class, it can be considered a "strict final" field. It will never be observed to mutate.
>
> In the class file, ACC_STRICT is repurposed to indicate a strict final field. javac is responsible for identifying strict final fields. Existing early-initialized capture fields can probably be automatically counted as strict finals.
>
> ACC_STRICT implies ACC_FINAL and !ACC_STATIC. Verification ensures that a 'putfield' for an ACC_STRICT field of the current class never occurs after the 'super()' call. (Specifically, the receiver type for the putfield must be 'uninitializedThis', not a class type.)
Although there are limits to how much we can do with the flag just yet (see below), I think it probably makes sense to identify these fields in class files as ACC_STRICT as part of this JEP.
> 3) Immutability of strict finals is a strong guarantee.
This jumps the gun.
ACC_STRICT is a claim about local code: that the field will not be mutated by the code of the class after the 'super()' call.
A global claim about immutability relies on other integrity properties of the JDK as a whole. There's a path to getting those integrity properties, but it's beyond the scope of this JEP.
> JVM internals may treat strict final fields as truly immutable, without supporting any deopt paths when unexpected mutation occurs.
This will be true only after we can make a global claim about immutability (ACC_STRICT, so not mutated by the class itself; plus all off-label mutation paths have been blocked or disavowed.)
> The 'Field.setAccessible' method, which provides a standard API mechanism for mutating final fields, considers strict finals to be "non-modifiable", and will not enable reflective writes. (It already does the same for record fields.)
This felt too ad hoc.
A better path is to follow in the footsteps of "Prepare to Restrict the Use of JNI" (https://openjdk.org/jeps/8307341), gradually limiting the use of 'setAccessible' for final field mutation (ACC_STRICT or not) to users who explicitly opt in. That would be a separate effort.
> Standard deserialization ensures strict finals are set, and so their values deserialized, before the object under construction is leaked to any user code. This probably means back references to an object from its own strict final fields are unsupported, and deserialize to 'null'. (Records already behave in this way.)
I think this would be nice to have, paired with ACC_STRICT, but we'll see whether we can get there or not. If not, it's an improvement that can come later.
> Unsafe and JNI are capable of performing arbitrary, type-unsafe modifications to field storage. Clients who modify strict finals do so at their own risk, and JVM optimizations won't try to account for such usage.
I noted the attempts to restrict use of JNI above; but in any case, I think this statement is still fair: JVM internals do not need to account for misuse of Unsafe/JNI.
> - Can javac check for me that my fields are strict?
I heard a proposed rule that if any constructor early-initializes a final field, the field should automatically be ACC_STRICT and an error should occur if a different constructor fails to early initialize the field.
I think that gets the new capability backward: it's not that we want to give people a new kind of field. Instead, we want the code in constructor prologues to have the flexibility to write to fields of 'this', because a broad prohibition on all uses of 'this' (including field writes, type variable use, enclosing instance access) is too blunt a restriction. *Secondarily*, we can notice that certain field initializations follow a pattern that represents a useful property, and we can document that in class files.
It's reasonable to want a feature that, as a matter of good style, checks fields for early initialization. But that capability isn't the core of the feature, and because there are various ways to get there, we're leaving it for Phase 2.
More information about the amber-spec-experts
mailing list