Loosening requirements for super() invocation
John Rose
john.r.rose at oracle.com
Thu Jan 26 02:05:09 UTC 2023
On 25 Jan 2023, at 15:06, Archie Cobbs wrote:
> On Wed, Jan 25, 2023 at 3:57 PM Brian Goetz <brian.goetz at oracle.com>
> wrote:
>> … Doing
>> a 40% smaller feature (just going by your "3 out of 5" estimate) at
>> 95%
>> lower cost (making up a number here, but the difference appears
>> significant) seems worth considering carefully.
>
> Gotcha. I think I need to spend time getting more familiar with the
> spec,
> so I can better understand how it's put together, what the trade-offs
> are,
> etc.
Brian is right; usually these sort of proposals break into layers, and
in many cases the first 80% is where we stop.
That said, I’d like to put on my DA/DU-expert hat for moment. (I
invented the DU part of that analysis for blank finals in 1.1.) This
analysis is subtle, understood by nearly nobody. For something that
subtle, it delivers amazing value, because javac teaches the users what
works and what doesn’t. The result is a very flexible language, and
yet the danger areas (uninitialized values) are strongly fenced away
from the user.
I have always thought that the super() syntax restriction (the “pinch
point rule”) is a simple tactic to get the same job done as assigning
DA/DU bits to “this”. It’s a blunt instrument compared with the
DA/DU analysis, and the spec. for it reads bluntly and simply. You’ll
not be surprised that I am intrigued (though not convinced) about the
alternative idea of leaning hard on new rules regarding DA/DU for
“this”.
I’m pretty sure either tactic could be made to work in the JLS. I’m
a little more hopeful than Brian that DA/DU bits would do the job well,
but sometimes a sledgehammer is better than a samurai sword. What I’m
wondering now is, if we use the sledgehammer technique, what is the
total “diff weight” of the spec.? Is it just a few touches or many?
(It could be many, if various odd corner cases need special treatment,
due to the bluntness of the instrument.) Likewise, if we carefully
re-slice the DA/DU reasoning to cover “this”, what would the total
“diff weight” be?
I have an unrelated suggestion (which makes sense with either tactic).
The existing DA/DU rules for blank finals are (I think) easy to adapt to
allow blank finals initializations to be hoisted above super-init. So
far so good. For non-finals (*mutable* non-static fields), may I
suggest that, if you were to allow initializations to them above
super-init, that you formulate the restrictions on their treatment to
align them with blank finals. Something like this:
- Before super-init, any *local* field, *without an initializer*
(whether final or not) is put into the DU state. (Today it is blank
finals only.)
- Before super-init, no local fields may be read, and only DU fields
may be written. (The JVM is slightly more lenient; we don’t care.)
- Assignments to any field in a DU state put it into the DA state.
(After that any further access must occur after super-init.)
- Before super-init, the names `this.f` and `f`, where `f` means
`this.f`, are treated identically, and there is no other expression
which can be used to assign to `f`. (This is true for finals after
super-init as well.)
- Immediately after super-init, DA and DU bits are discarded for all
non-final variables. This is also the place where field initializers
and instance initializers are executed.
- And of course, other than the above (special pleading for local
blank fields), no uses of `this`, either explicit or implicit, are
allowed. This may be because the code is textually before the pinch
point, or else because “this” is not DA until the super-init call.
All this might be in the last 20%, beyond the good 80% we might well
adopt. But I did want to put it out there.
It is interesting to note that, given the restrictions on local fields
in the “early” code, there is almost no way to tell if the field
assignments (the permitted early assignments to blank fields) are
actually committed to the object, or are just stashed into locals.
The only way you can tell if a field is actually committed is for an
unusual things to happen. The super-init method has to turn around and
access that field, either directly (via a cast), or (more likely) via an
instance method with an override in the subclass (which makes a secret
cast during virtual dispatch). This is rare but non-unheard-of, and
uplevel links of inner classes are sometimes used this way, by
super-init calls to virtuals. I think it would be good (if possible) to
give this tool to source programmers, and not just to hardwired
inner-class links. One more point: This use case could supported in
either way: Either eagerly write assigned field values at the
assignment point, or stash them in locals, and commit them all just
before the super-init call, which is where the possibility of observing
the fields begins.
This leads me to one more observation: Blank finals can be observed in
their uninitialized state during a super-init call. This is a hole in
the otherwise strong fence around final variables, and it sometimes
causes bad bugs. It will make the fence around finals stronger if we
allow them to be initialized before the super-init call. That is, if
users take the chance of initializing their blank finals before the
super-init call, then there is a very strong proof that no such blank
final will ever be observed in its uninitialized state. To me that’s
an indication that we should put this feature in the 80% rather than the
20%. The blank non-finals can come along for the ride also, maybe, or
maybe they belong in the 20% remainder. If that’s the case, some of
my suggestions above (but not all) are still relevant.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230125/da1fa3e2/attachment-0001.htm>
More information about the amber-dev
mailing list