JDK-8322510 : ClassCastException when using extra boolean parameter in invokedynamic bootstrap
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 8,11,17,21,22
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: linux
  • CPU: x86_64
  • Submitted: 2023-12-19
  • Updated: 2023-12-21
  • Resolved: 2023-12-20
Description
ADDITIONAL SYSTEM INFORMATION :
Linux / Java 21.0.1

A DESCRIPTION OF THE PROBLEM :
When generating bytecode that uses the invokedynamic instruction, if an extra boolean parameter is passed to an invokedynamic bootstrap method, a ClassCastException will result:

Caused by: java.lang.BootstrapMethodError: bootstrap method initialization exception
	at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:188)
	at java.base/java.lang.invoke.CallSite.makeSite(CallSite.java:316)
	at java.base/java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:274)
	at java.base/java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:264)
	at HelloWorld.run(Unknown Source)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	... 2 more
Caused by: java.lang.ClassCastException: Cannot cast java.lang.Integer to java.lang.Boolean
	at java.base/java.lang.Class.cast(Class.java:4067)
	at java.base/sun.invoke.util.ValueConversions.primitiveConversion(ValueConversions.java:252)
	at java.base/sun.invoke.util.ValueConversions.unboxBoolean(ValueConversions.java:108)
	at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:109)
	... 7 more

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached source code.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
"Hello, World!" will be printed
ACTUAL -
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.robolectric.preinstrumented.InvokeDynamicTest.main(InvokeDynamicTest.java:59)
Caused by: java.lang.BootstrapMethodError: bootstrap method initialization exception
	at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:188)
	at java.base/java.lang.invoke.CallSite.makeSite(CallSite.java:316)
	at java.base/java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:274)
	at java.base/java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:264)
	at HelloWorld.run(Unknown Source)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	... 2 more
Caused by: java.lang.ClassCastException: Cannot cast java.lang.Integer to java.lang.Boolean
	at java.base/java.lang.Class.cast(Class.java:4067)
	at java.base/sun.invoke.util.ValueConversions.primitiveConversion(ValueConversions.java:252)
	at java.base/sun.invoke.util.ValueConversions.unboxBoolean(ValueConversions.java:108)
	at java.base/java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:109)
	... 7 more

---------- BEGIN SOURCE ----------
package some.package;

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodNode;

public class InvokeDynamicTest {
  public static void main(String[] args) throws Exception {
    System.err.println(System.getProperty("java.version"));

    ClassNode classNode = new ClassNode();
    classNode.version = Opcodes.V1_8;
    classNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER;
    classNode.name = "HelloWorld";
    classNode.superName = "java/lang/Object";

    // Create the run method
    MethodNode runMethod = new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "run", "()V", null, null);
    InsnList instructions = runMethod.instructions;

        String bootstrapDescriptor = Type.getMethodDescriptor(Type.getType(CallSite.class),
            Type.getType(MethodHandles.Lookup.class),
            Type.getType(String.class),
            Type.getType(MethodType.class),
            Type.getType(boolean.class));

    Handle bootstrapMethod = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(
        InvokeDynamicTest.class), "bootstrap", bootstrapDescriptor, false);

    // Use invokedynamic to print "Hello, World!"
    instructions.add(new InvokeDynamicInsnNode("runMethod", "()V", bootstrapMethod, true));
    instructions.add(new InsnNode(Opcodes.RETURN));
    classNode.methods.add(runMethod);

    // Generate the bytecode
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
    classNode.accept(cw);
    byte[] bytecode = cw.toByteArray();

    // Define the generated class
    Class<?> helloWorldClass = new MyClassLoader().defineClass("HelloWorld", bytecode);

    // Invoke the run method of the generated class
    Method run = helloWorldClass.getMethod("run");
    run.invoke(null);
  }

  public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type, boolean extraBoolean) throws NoSuchMethodException, IllegalAccessException {
    MethodHandle mh = lookup.findStatic(InvokeDynamicTest.class, "dynamicMethod", type);
    return new ConstantCallSite(mh);
  }

  public static void dynamicMethod() {
    System.out.println("Hello, World!");
  }

  static class MyClassLoader extends ClassLoader {
    public Class<?> defineClass(String name, byte[] b) {
      return defineClass(name, b, 0, b.length);
    }
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The workaround is to use 'int' instead of 'boolean' as the extra argument for the invokedynamic bootstrap method.

FREQUENCY : always



Comments
The API of ASM is not used correctly so an integer is generated instead of a boolean so there is no bug.
20-12-2023

It's an ASM API misuse, ASM does not allow boolean as constant for an invokedynamic new InvokeDynamicInsnNode("runMethod", "()V", bootstrapMethod, true) // last argument is invalid see https://asm.ow2.io/javadoc/org/objectweb/asm/tree/InvokeDynamicInsnNode.html#%3Cinit%3E(java.lang.String,java.lang.String,org.objectweb.asm.Handle,java.lang.Object...) The classfile format does not allow boolean (or Boolean) see https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.4 If the code is modified to use a CheckClassAdapter https://asm.ow2.io/javadoc/org/objectweb/asm/util/CheckClassAdapter.html, ASM will report an error.
20-12-2023

The observations on Windows 11: JDK 8: Failed, ClassCastException: Cannot cast java.lang.Integer to java.lang.Boolean thrown JDK 11: Failed. JDK 17: Failed. JDK 21: Failed. JDK 22ea+16: Failed.
20-12-2023