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