Behavior of MethodHandles.convertArguments

John Rose john.r.rose at oracle.com
Sat May 21 23:18:23 PDT 2011


On May 15, 2011, at 9:40 AM, Raffaello Giulietti wrote:

> I would expect a WrongMethodTypeException to be thrown by
> convertArguments in the following snippet, on the ground that String
> is neither a wrapper nor a supertype of a wrapper of int, the return
> type of mh0.
> 
>        MethodHandle mh0 = lookup.findVirtual(String.class, "length",
> MethodType.methodType(int.class));
>        MethodHandle mh1 = MethodHandles.convertArguments(mh0,
> MethodType.methodType(String.class, String.class));
> 
> However, build 142 of the JDK 7 (2011-05-12) executes the code without
> errors. Is this expected behavior?

This should throw WMT.

But, a chained conversion from (String)int to (String)Object to (String)Number must succeed, producing a method handle that is doomed to throw CCE.

This should be fixed in build 144.

(Note:  convertArguments is being removed, since asType does the same job.  To factor out the effect of variable-arity method handles on asType, there will be MH.asFixedArity, so that convertArgs(x,y) := x.asFixedArity().asType(y).)

The reverse case, of reference to primitive, is a curious question.  Suppose we have:

       MethodHandle mh0 = lookup.findVirtual(Integer.class, "toString",
           methodType(String.class))
           .asType(methodType(String.class, int.class));
       // mh0(int x) := ((Integer)x).toString()
       MethodHandle mh1 = mh0.asType(
           methodType(String.class, String.class));

Here is another case where there is no hope of this conversion succeeding, and so asType should throw WMT instead of creating mh1.

But this one must work, obviously:

       MethodHandle mh2 = mh0.asType(methodType(String.class, Integer.class));

More interestingly, this also works:

       MethodHandle mh3 = mh0.asType(methodType(String.class, Object.class));

The semantics of Object->int unboxing is based on java.lang.reflect.Method.invoke.  This means that the unboxing can happen from any of the types Byte, Short, Character, or Integer:

       MethodHandle mh4 = mh0.asType(methodType(String.class, Byte.class));
       MethodHandle mh5 = mh0.asType(methodType(String.class, Character.class));

But this one needs to fail with WMT, since there is no way to unbox and widen to get an int:
       MethodHandle mh6 = mh0.asType(methodType(String.class, Long.class));

Looking back at mh3, we realize that it can fail dynamically in a similar way, if passed a value which is not a boxed byte, char, short, or int:
       mh3.invoke(42);  // OK: int -> Integer -> Object -> Integer -> int
       mh3.invoke((byte)4);  // OK: byte -> Byte -> Object -> Byte -> byte -> int
       mh3.invoke(0x420000000L);  // FAIL: long -> Long -> Object -> Integer CCE
       mh3.invoke("asdf");  // FAIL: String -> Object -> Integer CCE

These rules for asType arise from a mix of concerns:
 - the normal Java method invocation conversions (JLS 5.3) must be supported, at a minimum
 - when the target is a reference type, Java casting conversion (JLS 5.5) must be supported also
 - when the target is a primitive type, the conversions supplied by java.lang.reflect must be supported also

The union of these rules preserves some important general symmetries:
 - Object is always a universal source and destination type
 - every possible retyping of references is allowed (subject to runtime type checks)
 - wrapper types can be freely interconverted with their corresponding primitive types
 - primitives will never be narrowed (truncated, discarding sign or magnitude bits)

-- John



More information about the mlvm-dev mailing list