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@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev

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

Reply via email to