invokespecial-super-init

Jochen Theodorou blackdrag at gmx.org
Fri Sep 18 12:42:59 UTC 2015


Am 17.09.2015 19:57, schrieb John Rose:
> On Aug 29, 2015, at 4:42 AM, Jochen Theodorou <blackdrag at gmx.org>
[...]
> This leads to a natural pattern:  If the user wants to generate
> dynamically selected calls to two or more overloadings of S.<init>,
> wrap all of the S.<init> in corresponding C.<init> overloadings.  Add
> extra arguments to the C.<init> to initialize each of the finals of
> C.  When groovyc generates a class C, have it generate those wrapper
> constructors.  Then there is no need to create them in
> MethodHandles.
[...]

I am skipping to your POC code here:

> class S { S(int x){…}, S(String y){…} …}  // fixed API, not generated by Groovy
> interface WC { } // marker type for wrapper constructors
> class C {  // generated by Groovy
>   final char p, q;
>   private static class Finals { final char p, q; }
>   private C(WC ig, int x, MethodHandle finals) { super(x); Finals f = finals.invokeExact(this); this.p=finals.p; this.q=finals.q; }
>   private C(WC ig, String y, MethodHandle finals) { super(y); Finals f = finals.invokeExact(this); this.p=finals.p; this.q=finals.q; }
>   public C(int x, boolean z) { this(null, x, MH.bindTo(z)); }
>   public C(String y, boolean z) { this(null, y, MH.bindTo(z)); }
>   // public C(DynamicArgList dynamicArgs) { this(no can do!); super(this neither!); }
> }
>
> This pattern is approximately as general as random bytecodes inside
> constructors, is reasonably compact, and does not require new method
> handle types or verifier rules.
>
> (Note that the MH "finals" is able to "see" the UI<C> under the type
> C.  It is supposed to treat it reasonably, just like constructor code
> is supposed to.  Since the wrapper constructors are marked private,
> it is impossible for untrusted parties to inject malicious MH code.
> The MH could be replaced by a private instance method, if there is no
> need to have a different MH at different construction sites.)
>
> What do you think?  Is this close to the workarounds you already
> use?

let's say we take the S from above and use this constructor for C:

C(int x, boolean z) {super(XY.foo(x)); this.z=z}

what we produce is roughly this:

Object[] arguments = new Object[]{XY.foo(x)}
int choice = GroovyRuntime.selectConstructor(C,superCallArguments)
switch(choice) {
    case 1: super((int) arguments[0]); break;
    case 2: super((String) arguments[0]); break;
}
this.z=z

I chose this one because it contains a method call for a super call 
argument. XY#foo. And of course this could be a more complex expressions 
including a ist unwrapping, but let us forget about the later one for a 
moment. The problem with XY.foo(x) is, that we won't know the return 
type of that, and even the declared type is potentially not what we 
need. So let us assume we produce a wrapper constructor like you 
suggest, but forget about the finals part for a moment:

>    private C(WC ig, int x) { super(x); }
>    private C(WC ig, String y) { super(y);  }

then the part that is calling that would be:

>   public C(int x, boolean z) { this(null, XY.foo(x)); }

but since we don't know if foo will return something compatible to int 
or String (or neither!) I cannot produce the invokespecial call here. Or 
assume this:

> interface A{}
> interface B{}
> class S {
>   S(A a){}
>   S(B b){}
> }
>
> class C extends S{
>   C(Object o){super(o)}
> }

there is no reasonable basis for choosing between S(A) and S(B). At one 
point dynamic method selection needs to get involved for the general 
case. Of course there are common special cases, in which we solve the 
problem without that:

> class S{
>   S(int x){...}
>   S(String y) {...}
> }
>
> class C extends S {
>   final p
>   C(int x, boolean z) {
>     super(x)
>     this.p = z
>   }
>   C(String x, boolean z) {
>     super(x)
>     this.p = z
>   }
> }

then Groovy will do exactly the same as Java with regards to super calls 
to S. We can do this, because x here has a static type of a final class, 
thus there cannot be a differing runtime type, and method selection can 
be done in the compiler already. And this case is very near the one you 
have shown... In other words, this is already a solved problem. We need 
a solution for the dynamic cases.... and fully dynamic case can even 
look like this:

> class C extends S {
>   C(args){super(*args)}
> }

this means the array or list stored in args is unwrapped and treated as 
if every single element has been written there. So for a 2-element array 
super(*args) is equal to super(args[0],args[1]). There is absolutely no 
chance to emulate that with a static construct.

But I don't really request a solution to the last case. But what I need 
is a solution for a non-trivial call like with XY#foo, where I don't 
know statically, what it will return.

And there is one more point with finals, that is bugging me:

> class C extends S {
>   final z
>   def y
>   C(int x, y) {
>     super(x)
>     this.y = y+1
>     this.z = this.y+2
>   }
> }

I am fully aware, that this is bad code, but Java and Groovy allow this. 
Don't ask me about safe publication here... probably not working... 
anyway... the point being: I have code between the super call and the 
assignment to the final variable, which depends on an somewhat 
initialized class. I don't see how that fits your idea.

[...]
> Yes, the GenericInstance, standing for an uninitialized C, would
> model the part of the lifetime of C which is inside C.<init>.  (This
> is a little like Peter Levarts's concept of a verifiable "story".)
> Call it UI<C>, with U = Uninitialized. The operations on the UI<C>
> would be similar to those on C, but they would try to avoid
> accidental publication of the C reference, until it was fully
> constructed (whatever that means).  This type-state is like the
> larval/adult distinction I blogged once.
>
> But, if you are willing to use wrapper constructors C.<init>, you
> don't need the extra types and states.  Is that enough for Groovy?

currently it does not look like it is enough.

>> The Verifier thus needs to acknowledge it to do that. And there
>> needs to be code, that takes the result of the GenericInstance and
>> then places the real instance in variable slot 0.
>
> This is the problem with both your and Peter's proposals:  It
> requires verifier changes.  Those scare me, because I've worked with
> the verifier long enough to know how verifier complexity translates
> directly into challenges to safety and sanity.

well, I was hoping that a change like I proposed, could be done by 
"short cutting" some parts of the verifier. So that no completely new 
rules are needed. But it would need to change, yes. looks like the 
debugger and the verifier are both code parts nobody likes to touch.

>> Since it is a two fold mechanism I cannot programatically do
>> anything with the GenericInstance object, but to reach it through.
>> Only the part unwrapping it can access the real instance (and also
>> check the class to be sure) and that would be VM code.
>
> If you sit down and write the rules for GI / UI<C>, if you want to
> accurately emulate everything that a C.<init> could do, I think you
> will find that there is nothing unique about UI, *except* the ability
> to initialize finals.
[...]

except for code operating on UI (or what it contains) to initialize 
finals yes. But imho that is quite a big exception, with a lot of 
meaning in the current memory model.

bye Jochen


-- 
Jochen "blackdrag" Theodorou
blog: http://blackdragsview.blogspot.com/



More information about the mlvm-dev mailing list