New proposal for #ReflectiveAccessToNonExportedTypes: Open modules & open packages
forax at univ-mlv.fr
forax at univ-mlv.fr
Fri Nov 11 15:09:35 UTC 2016
Hi Andrew,
welcome to the jungle of the java.lang.invoke API !
----- Mail original -----
> De: "Andrew Dinn" <adinn at redhat.com>
> À: "John Rose" <john.r.rose at oracle.com>
> Cc: "Remi Forax" <forax at univ-mlv.fr>, jigsaw-dev at openjdk.java.net
> Envoyé: Vendredi 11 Novembre 2016 14:43:53
> Objet: Re: New proposal for #ReflectiveAccessToNonExportedTypes: Open modules & open packages
> Hi John,
>
> I'm reviving an old (by 2-3 weeks) conversation here because it seems
> the right place to ask about how to deal with a disparity I found
> between the use of reflection vs method handles. I'm have retained the
> cc to jigsaw-dev because i) the conversation started there and ii) it
> relates to the question of how well Lookups and MethdoHandles can stand
> in for reflection. Perhaps that's too tenuous to merit a CC but I'm
> adopting the excuse that those who are only interested in more direct
> Jigsaw content can simply ignore the rest of this thread.
>
> The disparity I am seeing looks like it might just be an argument for
> Maurizio's new reflection API more than anything else but it may just be
> that I am doing something wrong. I'll present the problem in as reduced
> form as I can manage to extract from the Byteman implementation.
>
> Byteman includes a test to ensure that the type checker allows duck
> typing when a compatible array type is passed as input to a method. So,
> we have a test method
>
> public class TestArrayArgTypeCheck extends Test {
> . . .
> public void testArrayCall(String[] args)
> {
> log("inside testArrayCall");
> }
> }
>
> where log is defined by the parent class Test.
>
> I use a Byteman rule as follows
>
> RULE test array arg type check
> CLASS TestArrayArgTypeCheck
> METHOD testArrayCall
> AT ENTRY
> BIND test : Test = $this;
> IF TRUE
> DO test.log("args : " + java.util.Arrays.asList($args) );
> ENDRULE
>
> This rule is 'injected' at the start of the code for method
> testArrayCall -- it's not actually executed as inline bytecode, rather a
> callout to the rule interpreter executes the rule (out of line bytecode
> execution is also an option but I don't [yet?] need to add that
> complexity to the mix). In case you are unfamiliar with how Byteman
> operates I'll summarise its operation.
>
> $this and $args name bindings for values passed into the interpreter,
> respectively:
> the instance of TestArrayArgTypeCheck fielding the call to the
> method (which Byteman knows is of type TestArrayArgTypeCheck)
> the argument to testArrayCall (which Byteman knows is of type String[])
>
> the BIND clause binds a /rule-local/ variable test (of type Test) to
> the instance of TestArrayArgTypeCheck fielding the call to the method
>
> the DO clause
> passes $this to Arrays.asList()
> pastes the result into a String
> passes the String to Test.log
>
> So, the test ensures that Byteman's type checker accepts that the
> String[] argument passed to Arrays.asList(Object[]) is a legitimate
> argument without throwing a type exception. It doesn't really matter
> what the log output is but the test does check for an expected output
> and that is where the disparity arises.
>
> The call to the target method is made by the test code as follows
>
> . . .
> String[] ordinals = { "first", "second", "third" };
> log("calling testArrayCall");
> testArrayCall(ordinals);
> log("called testArrayCall");
> . . .
>
> When this is run on JDK8[-] I see this output which matches expectation:
>
> <log>
> calling testArrayCall
> args : [first, second, third]
> inside testArrayCall
> called testArrayCall
> </log>
>
> When I run this on JDK9 using a modified version of Byteman that relies
> on MethodHandles I see this output:
>
> <log>
> calling testArrayCall
> args : [[Ljava.lang.String;@36bc55de]
> inside testArrayCall
> called testArrayCall
> </log>
>
> So, the String[] array appears to have been wrapped in an Object[]
> before being passed on to Arrays.asList().
>
>
> On JDK8[-] I use reflection to execute the method call. So, essentially
> the code looks like this
>
> class MethodExpression {
> Method method;
> Expression recipient;
> List<Expression> arguments;
> . . .
> Object interpret(...)
> . . .
> Object recipientValue =
> (recipient != null
> ? recipient.interpret(...)
> : null);
> int argCount = arguments.size();
> Object[] argValues = new Object[argCount];
> for (int i = 0; i < argCount; i++) {
> argValues[i] = arguments.get(i).interpret(...);
> }
> . . .
> return method.invoke(recipientValue, argValues);
>
> The essential differences in the code that gets executed on JDK9
> (ignoring that I am inlining code here from different branches) are as
> follows:
>
> class MethodExpression {
> Method method;
> MethodHandle handle = getMethodHandle(method);
> List<Expression> arguments;
> . . .
> Object interpret(...)
> . . .
> Object recipientValue =
> (recipient != null
> ? recipient.interpret(...)
> : null);
> int argCount = arguments.size();
> Object[] argValues = new Object[argCount];
> for (int i = 0; i < argCount; i++) {
> argValues[i] = arguments.get(i).interpret(helper);
> }
> . . .
> if (recipient == null) {
> handle.invoke(argValues);
> } else {
> handle.invokeWithArguments(recipientValue, argValues);
> }
MH.invokeWithArguments doesn't work like Method.invoke,
a MethodHandle is a function so any invoke* on a method handle is a function call,
there is no method on a method handle that separates the receiver (what you call the recipientValue) from the arguments when performing a call.
MH.invokeWithArguments takes an array of arguments but because it is specified as a varargs you may think that it works like Method.invoke, but it is a trap,
it takes the receiver and the arguments altogether into the same array.
so with a Stream it's something like:
Object[] argValues = Stream.concat(
Optional.ofNullable(recipient).map(r -> r.interpret(helper)).stream(),
Arrays.stream(arguments).map(r -> r.interpret(helper))
).toArray(Object[]::new);
. . .
handle.invokeWithArguments(argValues);
>
> regards,
>
>
> Andrew Dinn
regards,
Rémi
> -----------
> Senior Principal Software Engineer
> Red Hat UK Ltd
> Registered in England and Wales under Company Registration No. 03798903
> Directors: Michael Cunningham, Michael ("Mike") O'Neill, Eric Shander
More information about the jigsaw-dev
mailing list