javajava-bytecode-asminvokedynamicjvm-bytecode

ASM cannot pass 'Type.INT_TYPE' as constant Bootstrap Method Argument


Consider the following code that is meant to generate an invokedynamic instruction using ASM:

// BOOTSTRAP = new Handle(->
// CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType methodType, Class<?> someClass)

mv.visitInvokeDynamicInsn("foo", "(I)I", BOOTSTRAP, Type.INT_TYPE);

When decompiling the generated class using ASMifier, the relevant line becomes

mv.visitInvokeDynamicInsn("foo", "(I)I", new Handle(/* SNIP (same as BOOTSTRAP) */),
                          Type.getType("LI;"));
                                       ¯¯¯¯¯

As you can see, the Type.INT_TYPE has turned into a literal reference to a reference type named I. As this doesn't exist, the JVM complains at runtime with java.lang.BootstrapMethodError: java.lang.NoClassDefFoundError: I.

What I wanted to do instead was pass int.class (the Class instance for the primitive type int, or the value of the Integer.TYPE constant) to my bootstrap method as the argument for someClass. However, it seems like ASM did not properly understand or support this.

Can this be considered an ASM bug, and is there a workaround for this?


Solution

  • As Brett Kail pointed out, it is impossible to encode a Class constant for a primitive type. When you use a literal like int.class in source code, the compiler will encode it as read operation of the field java.lang.Integer.TYPE, which contains the desired Class object. For annotations, it is possible, because the annotation value is encoded to point to a CONSTANT_Utf8_info containing a return descriptor rather than a CONSTANT_Class_info (see JVM spec §4.7.16.1).

    Since encoded static arguments to bootstrap methods require Class objects to be encoded as CONSTANT_Class_info, they do not support primitive types. See JVM spec §4.7.23:

    Each entry in the bootstrap_arguments array must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_String_info, CONSTANT_Class_info, CONSTANT_Integer_info, CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info, CONSTANT_MethodHandle_info, or CONSTANT_MethodType_info structure…

    A work-around would be to add a convention, e.g. always encode the array type of the desired type and extract the element type in the bootstrap method. Or encode the desired type as the return type of a CONSTANT_MethodType_info. The latter has the advantage of even supporting void.class.