Ben Weidig created TAP5-2807: -------------------------------- Summary: Plastic: NoClassDefFoundError: I (ClassNotFoundException: I) when InstructionBuilder processes 'int' type information in certain sequences Key: TAP5-2807 URL: https://issues.apache.org/jira/browse/TAP5-2807 Project: Tapestry 5 Issue Type: Bug Components: plastic Affects Versions: 5.9.0 Reporter: Ben Weidig
h2. Problem Plastic's InstructionBuilder (or maybe the subsequent class finalization/loading process) can lead to a java.lang.NoClassDefFoundError: I at runtime. This error originates from PlasticClassLoader, indicating an attempt to load a class literally named "I" (the JVM's internal type descriptor for int). Other primitives might be affected as well, but I encountered it while working with int; h2. Context & Trigger I was trying to use the InstructionBuilder in PropertyConduitSourceImpl to implement the RANGEOP for subexpressions, meaning support for expressions with int primitives for the IntegerRange(int from, int to) constructor as part of a list literal like "[true, 1..2]". The arguments for the IntegerRange constructor were derived from a sub-expression, requiring a sequence of InstructionBuilder calls to: # Evaluate sub-expressions (e.g., an INTEGER node from ANTLR, initially yielding a long). # Box primitives if necessary (e.g., long to Long using builder.boxPrimitive("long")). # Potentially coerce object types to java.lang.Integer (e.g., using builder.invoke() on a delegate method that takes int.class as a Class parameter via builder.loadTypeConstant(int.class)). # Ensure a java.lang.Integer object is on the stack (using builder.checkcast(Integer.class)). # Obtain an int primitive from the Integer object (e.g., using builder.invoke(Integer.class.getMethod("intValue")) because builder.unboxPrimitive("int") also triggered this error). h2. Key Findings *Simple Constructor Invocation Works:* Generating bytecode to call new IntegerRange(1, 2) using {code:java} builder.loadConstant(1); builder.loadConstant(2); builder.invokeConstructor(IntegerRange.class, int.class, int.class); {code} does not trigger the NoClassDefFoundError: I. This suggests InstructionBuilder.invokeConstructor() itself handles int.class argument types correctly for descriptor generation. *Error Linked to Type Handling Sequence:* The NoClassDefFoundError: I only appears when the more complex sequence of InstructionBuilder calls (boxing, coercion involving loadTypeConstant(int.class), checkcasting, and obtaining the int primitive via invoke(Integer.intValue())) is used to prepare arguments for the IntegerRange constructor. *Error Timing:* Diagnostic System.out.println statements confirm that all InstructionBuilder calls in the problematic sequence _appear_ to complete. The NoClassDefFoundError: I occurs later, when the generated method (e.g., the PropertyConduit.get()) is actually executed. h2. Hypothesis The NoClassDefFoundError: I suggests that one or more InstructionBuilder methods, when processing int type information (either as a string like "int", a Class object like int.class, or as a return/parameter type of an invoked method), has an unintended side-effect. This likely involves Plastic's internal type system or class finalization process incorrectly interpreting the JVM descriptor "I" as a class name to be loaded by PlasticClassLoader. The specific InstructionBuilder methods involved in the failing sequence include: * builder.boxPrimitive(String primitiveTypeName): when primitiveTypeName is "long" but the underlying issue might be general to how primitive type strings are handled if "int" also causes it in other contexts * builder.loadTypeConstant(Class primitiveClass): when primitiveClass is int.class * builder.invoke(Method method): when the method involves int.class as a parameter type, or returns a primitive int like Integer.intValue() * The previously attempted builder.unboxPrimitive("int") also led to the error. h2. Impact This potential bug in Plastic makes it difficult to reliably generate bytecode that involves handling and converting various types to int primitives using the InstructionBuilder API, hindering the implementation of features that require such dynamic code generation. In my case, I couldn't implement the RANGEOP for subexpressions. h2. Steps to Reproduce # Obtain an InstructionBuilder for a method. # Execute a sequence of operations similar to the "Context & Trigger" section: ** Push a long primitive. ** Call builder.boxPrimitive("long"). ** Call builder.loadTypeConstant(int.class). ** Simulate a coercion call that uses this int.class constant and results in an Integer object. ** Call builder.checkcast(Integer.class). ** Call builder.invoke(Integer.class.getMethod("intValue")). # Use the resulting int as an argument to another method or constructor. # Instantiate and invoke the generated method. # Observe java.lang.NoClassDefFoundError: I. -- This message was sent by Atlassian Jira (v8.20.10#820010)