Class verifier issues with unboxing method handles

John Rose john.r.rose at oracle.com
Thu Jun 13 18:55:28 PDT 2013


A REF_newInvokeSpecial method handle constant refers to a void-returning method named <init>, but its bytecode behavior returns a reference to the constructed object.

This may require special checks, such as with 'actualReturnType' in AbstractValidatingLambdaMetafactory.java.

There is a missing check in InnerClassLambdaMetafactory.java.  Here is a suggested fix (which I have not tested).

— John

diff --git a/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
--- a/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
+++ b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java
@@ -112,7 +112,10 @@
         implMethodDesc = implMethodType.toMethodDescriptorString();
         Type implMethodAsmType = Type.getMethodType(implMethodDesc);
         implMethodArgumentTypes = implMethodAsmType.getArgumentTypes();
-        implMethodReturnType = implMethodAsmType.getReturnType();
+        if (implKind == MethodHandleInfo.REF_newInvokeSpecial)
+            implMethodReturnType = Type.getType(implMethodClassName);
+        else
+            implMethodReturnType = implMethodAsmType.getReturnType();
         constructorType = invokedType.changeReturnType(Void.TYPE);
         constructorDesc = constructorType.toMethodDescriptorString();
         lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();


On May 29, 2013, at 4:25 AM, Maurizio Cimadamore <maurizio.cimadamore at oracle.com> wrote:

> David, Mark,
> a minimal test case to reproduce the issue is this:
> 
> interface IntFunction<X> {
>    int m(X x);
> }
> 
> class Test {
>    public static void main(String[] args) {
>       IntFunction<String> s = Integer::new;
>    }
> }
> 
> 
> Looking at the javap output, in particular at the indy call details, we 
> find this:
> 
> BootstrapMethods:
>     0: #15 invokestatic 
> java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>       Method arguments:
>         #16 invokeinterface IntFunction.m:(Ljava/lang/Object;)I
>         #17 newinvokespecial 
> java/lang/Integer."<init>":(Ljava/lang/String;)V
>         #18 (Ljava/lang/String;)I
> 
> 
> This seems correct; however, judging from the runtime error, it seems 
> like the metafactory is not unboxing the Integer instance back to int 
> (which is the expected return value of the implemented method). Hence 
> the 292 link failure. Robert, Brian can you confirm?
> 
> Maurizio
> 
> 
> On 29/05/13 02:48, David Holmes wrote:
>> Hi Mark,
>> 
>> cc'ing lambda-dev. This may be a bug, a version mismatch, or something
>> else. The lambda-dev folk will know.
>> 
>> David
>> 
>> On 29/05/2013 11:04 AM, Mark Derricutt wrote:
>>> Hi all,
>>> 
>>> Mark Reinhold suggested I posted this question/bug report to hotspot-dev
>>> rather than jdk8-dev so here goes.
>>> 
>>> I have a fairly simple test case using the new streams API:
>>> 
>>> public static void main(String[] args) {
>>> List<String> strings = Arrays.asList("1", "2", "3", "4", "5");
>>>    strings.stream()
>>> .mapToInt(s -> new Integer(s))
>>> .forEach(i -> System.out.println(String.format("int %d", i)));
>>> }
>>> 
>>> 
>>> which compiles, runs, and prints out what I expect:
>>> 
>>> int 1
>>> int 2
>>> int 3
>>> int 4
>>> int 5
>>> 
>>> 
>>> Given that mapToInt() is returning an IntegerStream wrapping primitive
>>> ints, the result of my closure is successfully autoboxed down to an int
>>> and all is happy with the world.
>>> 
>>> If I change this code to be:
>>> 
>>>    strings.stream()
>>>           .mapToInt(Integer::parseInt)
>>>           .forEach(i -> System.out.println(String.format("int %d", i)));
>>> 
>>> Again, everything works as expected as Integer::parseInt returns an int,
>>> so there's no boxing.
>>> 
>>> Changing this once again to:
>>> 
>>>    strings.stream()
>>>           .mapToInt(Integer::valueOf)
>>>           .forEach(i -> System.out.println(String.format("int %d", i)));
>>> 
>>> I also see the expected result, Integer::valueOf returns an Integer
>>> which is then being boxed down to an int.
>>> 
>>> However, if I change the code to:
>>> 
>>>   strings.stream()
>>>          .mapToInt(Integer::new)
>>>          .forEach(i -> System.out.println(String.format("int %d", i)));
>>> 
>>> which, if I understand correctly - Integer::new should be returning a
>>> method handle to the constructor. IntelliJ IDEA 13 autocompletes this
>>> for me, and hyperlinks to the Integer(String s) constructor, javac
>>> successfully compiles the code so my -assumption- is that the
>>> constructor would be called, returning an Integer which is then boxed
>>> down to an int, however, this is what I get:
>>> 
>>> Exception in thread "main" java.lang.BootstrapMethodError: call site
>>> initialization exception
>>>      at java.lang.invoke.CallSite.makeSite(CallSite.java:298)
>>>      at
>>> java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:294)
>>> 
>>>      at com.talios.test.TestJdk.main(TestJdk.java:12)
>>>      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>      at
>>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
>>> 
>>>      at
>>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
>>> 
>>>      at java.lang.reflect.Method.invoke(Method.java:491)
>>>      at
>>> com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
>>> Caused by: java.lang.VerifyError: Bad type on operand stack
>>> Exception Details:
>>>    Location:
>>>      com/talios/test/TestJdk$$Lambda$1.applyAsInt(Ljava/lang/Object;)I
>>> @11: ireturn
>>>    Reason:
>>>      Type 'java/lang/Integer' (current frame, stack[0]) is not
>>> assignable to integer
>>>    Current Frame:
>>>      bci: @11
>>>      flags: { }
>>>      locals: { 'com/talios/test/TestJdk$$Lambda$1', 'java/lang/Object' }
>>>      stack: { 'java/lang/Integer' }
>>>    Bytecode:
>>>      0000000: bb00 0e59 2bc0 0010 b700 13ac
>>> 
>>>      at java.lang.Class.getDeclaredConstructors0(Native Method)
>>>      at java.lang.Class.privateGetDeclaredConstructors(Class.java:2536)
>>>      at java.lang.Class.getDeclaredConstructors(Class.java:1928)
>>>      at
>>> java.lang.invoke.InnerClassLambdaMetafactory$1.run(InnerClassLambdaMetafactory.java:147)
>>> 
>>>      at
>>> java.lang.invoke.InnerClassLambdaMetafactory$1.run(InnerClassLambdaMetafactory.java:144)
>>> 
>>>      at java.security.AccessController.doPrivileged(Native Method)
>>>      at
>>> java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:143)
>>> 
>>>      at
>>> java.lang.invoke.LambdaMetafactory.metaFactory(LambdaMetafactory.java:191)
>>>      at java.lang.invoke.CallSite.makeSite(CallSite.java:283)
>>>      ... 7 more
>>> 
>>> This is on OSX Mountain Lion, with JDK 8 Build 91.
>>> 
>>> Have I walked in an obscure corner case of method handle breakage and
>>> found something new, or is this a new problem?
>>> 
>>> Cheers,
>>> Mark
>>> 
> 
> 



More information about the lambda-dev mailing list