Am 17.09.2015 19:57, schrieb John Rose:
On Aug 29, 2015, at 4:42 AM, Jochen Theodorou <blackd...@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/

_______________________________________________
mlvm-dev mailing list
mlvm-dev@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev

Reply via email to