2

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?

Clashsoft
  • 11,553
  • 5
  • 40
  • 79

3 Answers3

4

I do not believe it is possible to pass a primitive type as a bootstrap method argument because it is not possible to encode them in the class file. Per the JVM spec, class arguments are represented as CONSTANT_Class_info, which can only represent names in internal form, not as a descriptor.

Edit: There is a proposal to support primitive type constants:

This minimal prototype adopts this answer, using semicolon ; (ASCII decimal code 59) as the escape character. Thus, the types int.class and void.class may now be obtained class-file constants with the UTF8 strings ";I" and ";V". The choice of semicolon is natural here, since a class name cannot contain a semicolon (unless it is an array type), and descriptor syntax is often found following semicolons in class files.

Brett Kail
  • 33,593
  • 2
  • 85
  • 90
3

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.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
0

If you use a dynamic constant to load the field once, and feed the dynamic constant in where you would otherwise use Type.getType(), you can get an effect almost exactly like having primitive class constants directly in the class file.

There's a method which is specifically designed to facilitate this on java.lang.invoke.ConstantBootstraps. It's called primitiveClass. It is used like this:

ConstantDynamic intClassConstant =
    new ConstantDynamic(
        "I", 
        "Ljava/lang/Class;", 
        new Handle(
            Opcodes.H_INVOKESTATIC,
            "java/lang/invoke/ConstantBootstraps",
            "primitiveClass",
            // method descriptor strings can get long, apologies...
            "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Class;",
            false
        ));

Then, use that intClassConstant constant value anywhere you would want to use Type.getType(int.class). This works for any primitive type and void.

David M. Lloyd
  • 2,805
  • 1
  • 12
  • 10
  • Too much indirection. Consider using [`new ConstantDynamic("I", "Ljava/lang/Class;", new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/ConstantBootstraps", "primitiveClass", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Class;", false))`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/ConstantBootstraps.html#primitiveClass%28java.lang.invoke.MethodHandles.Lookup,java.lang.String,java.lang.Class%29). – Johannes Kuhn Jul 09 '22 at 02:45
  • You're right, that's a better solution. I'll update my answer. – David M. Lloyd Jul 12 '22 at 11:47