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