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