A pure Java example with reflective access behaving differently with invokeWithArguments() compared to core reflection's invoke() (Re: Strange observation: MethodHandle.invokeWithArguments() would not work, whereas Method.invoke() would with the very same arguments
Peter Levart
peter.levart at gmail.com
Fri Jun 8 16:23:46 UTC 2018
Hi Rony,
I think what you found is a discrepancy between handling of varargs
methods using reflection vs. method handle's .invokeWithArguments().
Reflection basically ignores the fact that some methods are varargs
methods. It treats them exactly the same as if they had an array
parameter (not a varargs parameter).
For example, Arrays.asList() is a varargs method:
public static <T> List<T> asList(T ... elements)
But reflection treats is exactly the same as the following method:
public static <T> List<T> asList(T[] elements)
That's because varargs were introduced to Java in Java 5 as a kind of
compilation sugar, implemented as an array parameter, while reflection
is an older beast and wasn't updated to behave differently with varargs
methods.
MethodHandle(s) came later, in Java 7, and took into consideration
varargs methods.
So where's the problem? When you say in Java:
Method asListMethod = Arrays.class.getMethod("asList",
Object[].class);
String[] elements = {"a", "b", "c"};
System.out.println(
asListMethod.invoke(null, elements)
);
You get: IllegalArgumentException: wrong number of arguments
Because Method.invoke() is also a varargs method:
public Object invoke(Object obj, Object... args)
... so java compiler (in order to be backwards source compatible with
pre-varargs methods) treats this invocation in a way that just passes
the String[] elements to the invoke method via the args parameter
without any conversion (because String[] is a subtype of Object[]).
Mathod.invoke then treats elements of the args[] array as individual
verbatim parameters to be passed to the method when invoking it. As
reflection treats Arrays.asList() as a normal no-varargs method which
accepts a single Objet[] parameter and args[] contains 3 elements to be
passed to a method with 3 parameters, exception occurs.
But when you use method handles and .invokeWithArguments():
Method asListMethod = Arrays.class.getMethod("asList",
Object[].class);
MethodHandle asListMH =
MethodHandles.lookup().unreflect(asListMethod);
String[] elements = {"a", "b", "c"};
System.out.println(
asListMH.invokeWithArguments(elements)
);
... the following happens: MethodHandle.invokeWithArguments() is also a
varargs method:
public Object invokeWithArguments(Object... arguments)
... so java compiler (in order to be backwards source compatible with
pre-varargs methods) treats this invocation in a way that just passes
the String[] elements to the invokeWithArguments method via the single
'arguments' parameter without any conversion (because String[] is a
subtype of Object[]). MethodHandle.invokeWithArguments therefore takes 3
array elements and tries to invoke the underlying asList method with
them. It observes that Arrays.asList is a varargs method, so it wraps
the 3 "wannabe parameters" with the Object[] and passes the array to the
asList method as single parameter. The result of above code is therefore:
[a, b, c]
If you want MethodHandle.invokeWithArguments() to treat "wannabe
parameters" passed to it as verbatim parameters regardless of the
variable/fixed arity of the method to be invoked, then you can transform
the variable arity MethodHandle to a fixed arity MH:
Method asListMethod = Arrays.class.getMethod("asList",
Object[].class);
String[] elements = {"a", "b", "c"};
System.out.println(
asListMethod.invoke(null, (Object) elements)
);
MethodHandle asListMH =
MethodHandles.lookup().unreflect(asListMethod);
asListMH = asListMH.asFixedArity();
System.out.println(
asListMH.invokeWithArguments((Object) elements)
);
Here you have it. Both of the above invocations produce equal output:
[a, b, c]
[a, b, c]
What happens here is the following (will only describe the MH case -
Method case is similar):
MethodHandle.invokeWithArguments() is a varargs method:
public Object invokeWithArguments(Object... arguments)
... so java compiler this time wraps the '(Object) elements' value with
an Object[] because the value is of Object type (which is not a subtype
of Object[]) - no backwards compatibility needed here, varargs
conversion kicks in in the javac. MethodHandle.invokeWithArguments
therefore takes an array with 1 element (the element being 'elements'
array) and tries to invoke the underlying asList method with it. This
time it observes that the method is not a varargs method, (because
MethodHandle was transformed to fixed arity method handle) so it passes
the single 'elements' wannabee parameter to the Arrays.asList single
Object[] parameter - this invocation works because fixed arity asList()
takes Object[] and 'elements' is a String[]...
Hope this helps you understand what's going on.
Regards, Peter
On 06/08/18 12:07, Rony G. Flatscher wrote:
> On 11.03.2018 20:22, Rony G. Flatscher wrote:
>> Well, still trying to find out what the reason is, that core
>> reflection's invoke behaves differently to MethodHandle's
>> invokeWithArguments in one single case so far (using the method
>> java.utli.Arrays.asList(...)).
>>
>> Here is a little Java program that excercises reflective access to
>> "java.util.Arrays.asList(T... a)" using core reflection, i.e.
>> "Method.invoke(Object obj, Object... args)" and
>> "MethodHandle.invokeWithArguments(Object... arguments)":
>>
>> import java.util.*;
>>
>> import java.lang.reflect.*;
>> import java.lang.invoke.*;
>>
>> class DemoAsListProblem
>> {
>> public static void main (String args[])
>> {
>> String arrNames[]=new String[] { "anne", "bert", "celine"};
>> System.out.println("[1] (main) arrNames=\""+arrNames+"\", .toString()=\""+arrNames.toString()+"\"");
>> List listNames=Arrays.asList(arrNames);
>> System.out.println("[2] (main) after invoking Arrays.asList(arrNames), listNames=\""+listNames+"\"");
>> System.out.println("\ninvoking testReflective() ...\n");
>>
>> testReflective();
>> }
>>
>> public static void testReflective()
>> {
>> String arrNames[]=new String[] { "anne", "bert", "celine"};
>> System.out.println("[3] (testReflective) arrNames=\""+arrNames+"\", .toString()=\""+arrNames.toString()+"\"");
>>
>> Method methAsList=null;
>> List listNames=null;
>> try {
>> Class paramTypes[]=new Class[] { (new Object[0]).getClass() };
>> methAsList=Arrays.class.getDeclaredMethod("asList", paramTypes);
>> System.out.println("--- (core reflection) Method object asList: "+methAsList);
>> System.out.println("\n--- (core reflection) now invoking Arrays.asList() via Method.invoke(...):");
>>
>> listNames=(List) methAsList.invoke( null, (Object[]) new Object[]{arrNames} ); // static method
>> System.out.println("[4a] --- (CR) methAsList.invoke( null, (Object[]) new Object[]{arrNames} ) -> listNames=\""+listNames+"\"");
>>
>> listNames=(List) methAsList.invoke( null, (Object) new Object[]{arrNames} ); // static method
>> System.out.println("[4b] --- (CR) methAsList.invoke( null, (Object) new Object[]{arrNames} ) -> listNames=\""+listNames+"\"");
>>
>> // "java.lang.IllegalArgumentException: wrong number of arguments":
>> // listNames=(List) methAsList.invoke( null, (Object[]) arrNames ); // static method
>> // System.out.println("[5a] --- (CR) methAsList.invoke( null, (Object[]) arrNames ) -> listNames=\""+listNames+"\"");
>>
>> listNames=(List) methAsList.invoke( null, (Object) arrNames ); // static method
>> System.out.println("[5b] --- (CR) methAsList.invoke( null, (Object) arrNames ) -> listNames=\""+listNames+"\"");
>> }
>> catch (Throwable t)
>> {
>> System.err.println("oops #1: "+t);
>> t.printStackTrace();
>> System.exit(-1);
>> }
>>
>> System.out.println("\n--- (MH) now invoking Arrays.asList() via MethodHandle.invokeWithArguments(...):");
>> MethodHandles.Lookup lookup=MethodHandles.lookup();
>> MethodHandle mh=null;
>> try {
>> mh=lookup.unreflect(methAsList);
>> System.out.println("--- (MH) unreflected MethodHandle mh: "+mh);
>>
>> listNames=(List) mh.invokeWithArguments( (Object[]) new Object[]{arrNames} );
>> System.out.println("[6a] --- (MH) mh.invokeWithArguments( (Object[]) new Object[]{arrNames} ) -> listNames=\""+listNames+"\"");
>>
>> listNames=(List) mh.invokeWithArguments( (Object) new Object[]{arrNames} );
>> System.out.println("[6b] --- (MH) mh.invokeWithArguments( (Object) new Object[]{arrNames} ) -> listNames=\""+listNames+"\"");
>>
>> listNames=(List) mh.invokeWithArguments( (Object[]) arrNames );
>> System.out.println("[7a] --- (MH) mh.invokeWithArguments( (Object[]) arrNames ) -> listNames=\""+listNames+"\"");
>>
>> listNames=(List) mh.invokeWithArguments( (Object) arrNames );
>> System.out.println("[7b] --- (MH) mh.invokeWithArguments( (Object) arrNames ) -> listNames=\""+listNames+"\"");
>>
>> }
>> catch (Throwable t)
>> {
>> System.err.println("oops #2: "+t);
>> t.printStackTrace();
>> System.exit(-2);
>> }
>> }
>> }
>>
>> Compiling and running it under 9.0.4 yields the following output:
>>
>> [1] (main) arrNames="[Ljava.lang.String;@27ddd392", .toString()="[Ljava.lang.String;@27ddd392"
>> [2] (main) after invoking Arrays.asList(arrNames), listNames="[anne, bert, celine]"
>>
>> invoking testReflective() ...
>>
>> [3] (testReflective) arrNames="[Ljava.lang.String;@2a18f23c", .toString()="[Ljava.lang.String;@2a18f23c"
>> --- (core reflection) Method object asList: public static java.util.List java.util.Arrays.asList(java.lang.Object[])
>>
>> --- (core reflection) now invoking Arrays.asList() via Method.invoke(...):
>> [4a] --- (CR) methAsList.invoke( null, (Object[]) new Object[]{arrNames} ) -> listNames="[anne, bert, celine]"
>> [4b] --- (CR) methAsList.invoke( null, (Object) new Object[]{arrNames} ) -> listNames="[[Ljava.lang.String;@2a18f23c]"
>> [5b] --- (CR) methAsList.invoke( null, (Object) arrNames ) -> listNames="[anne, bert, celine]"
>>
>> --- (MH) now invoking Arrays.asList() via MethodHandle.invokeWithArguments(...):
>> --- (MH) unreflected MethodHandle mh: MethodHandle(Object[])List
>> [6a] --- (MH) mh.invokeWithArguments( (Object[]) new Object[]{arrNames} ) -> listNames="[[Ljava.lang.String;@2a18f23c]"
>> [6b] --- (MH) mh.invokeWithArguments( (Object) new Object[]{arrNames} ) -> listNames="[[Ljava.lang.Object;@13a57a3b]"
>> [7a] --- (MH) mh.invokeWithArguments( (Object[]) arrNames ) -> listNames="[anne, bert, celine]"
>> [7b] --- (MH) mh.invokeWithArguments( (Object) arrNames ) -> listNames="[[Ljava.lang.String;@2a18f23c]"
>>
>> So a String array is turned into a List using
>> Arrays.asList(strArray). Doing it with core reflection yields
>> different results to doing it with invokeWithArguments().
>>
>> I would have expected that [4a] and [6a] would behave the same.
>>
>> Using reflective invoke() and invokeWithArguments() has been working
>> for my bridge for all the test units (using literally the same
>> arguments in both cases) interchangeably, the one exception is
>> Arrays.asList().
>>
>> Maybe I am not seeing the obvious (having done too much "close-up
>> tests" for quite some time now). So any hint, insight, help would be
>> really appreciated!
>>
>> ---rony
> As a few months have gone by without any follow-ups, I have been
> wondering whether there is a solution at all, if this is a problem
> rooted in the current implementation of MethodHandle methods not being
> 100% compatible with reflective invoke (which may imply that one needs
> to stick to use reflective invoke and forgo MethodHandle invokes for
> good).
>
> ---rony
>
>
>
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev at openjdk.java.net
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/mlvm-dev/attachments/20180608/c32edb03/attachment-0001.html>
More information about the mlvm-dev
mailing list