<div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">I would like to add that if you insert `cb.aload(storage); cb.astore(storage);` in the reproducer after the first `cb.astore(storage);`, the stack map changes so that the variable type in the exception handler is `java/lang/String` instead of `null`. So, something is definitely weird I think.</div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Wed, Nov 5, 2025 at 8:08 AM David Lloyd <<a href="mailto:david.lloyd@redhat.com">david.lloyd@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">We tripped a bytecode verifier error in Semeru (IBM JDK) when using stack map generation, but I'm starting to think this could actually be a classfile API bug which HotSpot's verifier happens to miss.</div><br clear="all"></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Basically, when there is an exception handler, and one of the control flow paths into the handler involves a local variable which is initialized using `ACONST_NULL`, then that local variable slot is defined to have a type of `null` in the exception handler, even if the variable is modified in the body of the `try` section to have some other type (like `Ljava/lang/String;` for example).</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Here's the meat of the bug report (the reproducer at the end is the useful bit):</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">The following program is verifiable on Temurin but not Semeru:<br><br>```<br>// the method: void com.acme.FailVerify#explode(String)<br>// max stack 1, max locals 3<br> 0: ACONST_NULL<br> 1: ASTORE_1<br> // try block starts here, catch at 8<br> 2: LDC "Hello!"<br> 4: ASTORE_1<br> // try block ends here, catch at 8<br> 5: GOTO 9<br> // catch here<br> // Stack map FULL_FRAME; locals: [java/lang/String, null]<br> 8: ASTORE_2 // UPDATE: it fails even if this is a POP<br> // control merges here<br> // Stack map FULL_FRAME; locals: [java/lang/String, java/lang/String]<br> 9: RETURN<br>```<br><br>The error looks like this:<br><br>```<br>java.lang.VerifyError: JVMVRFY021 thrown object not throwable; class=com/acme/FailVerify, method=explode(Ljava/lang/String;)V, pc=4<br>Exception Details:<br>  Location:<br>    com/acme/FailVerify.explode(Ljava/lang/String;)V @4: JBastore1<br>  Reason:<br>    Type 'java/lang/String' (current frame, locals[1]) is not assignable to null (stack map, locals[1])<br>  Current Frame:<br>    bci: @4<br>    flags: { }<br>    locals: { 'java/lang/String', 'java/lang/String' }<br>    stack: { 'java/lang/Throwable' }<br>  Stackmap Frame:<br>    bci: @8<br>    flags: { }<br>    locals: { 'java/lang/String', null }<br>    stack: { 'java/lang/Throwable' }<br>  Exception Handler Table:<br>    bci [2, 5] => handler: 8<br>  Stackmap Table:<br>    full_frame(@8,{Object[#2],null},{Object[#3]})<br>    full_frame(@9,{Object[#2],Object[#2]},{})<br><br>      at java.base/java.lang.J9VMInternals.prepareClassImpl(Native Method)<br>  at java.base/java.lang.J9VMInternals.prepare(J9VMInternals.java:312)<br>  at java.base/java.lang.Class.getMethodHelper(Class.java:1336)<br> at java.base/java.lang.Class.getMethod(Class.java:1269)<br>       (etc.)<br>```<br><br>A simple reproducer:<br><br>```java<br>package whatever;<br><br>import static java.lang.classfile.ClassFile.*;<br>import static java.lang.constant.ConstantDescs.*;<br><br>import java.lang.classfile.Attributes;<br>import java.lang.classfile.ClassFile;<br>import java.lang.classfile.Label;<br>import java.lang.classfile.TypeKind;<br>import java.lang.classfile.instruction.ExceptionCatch;<br>import java.lang.constant.ClassDesc;<br>import java.lang.constant.MethodTypeDesc;<br>import java.lang.reflect.AccessFlag;<br>import java.util.stream.Collectors;<br><br><br>public class MakeFailVerify {<br><br>    public static void main(String[] args) throws Exception {<br>        ClassFile cf = of(StackMapsOption.GENERATE_STACK_MAPS);<br><br>        byte[] bytes = cf.build(ClassDesc.of("com.acme.FailVerify"), zb -> {<br>            zb.withVersion(JAVA_17_VERSION, 0);<br>            zb.withMethod("explode", MethodTypeDesc.of(CD_void, CD_String), ACC_PUBLIC | ACC_STATIC, mb -> {<br>                mb.withFlags(AccessFlag.PUBLIC, AccessFlag.STATIC);<br>                mb.withCode(cb -> {<br>                    // - declare a variable init to null<br>                    // - try<br>                    // - assign non-null constant to variable<br>                    // - goto L3<br>                    // - catch any<br>                    // - store exception to new variable<br>                    // - L3: return<br>                    final Label L0, L1, L2, L3;<br>                    L0 = cb.newLabel();<br>                    L1 = cb.newLabel();<br>                    L2 = cb.newLabel();<br>                    L3 = cb.newLabel();<br>                    // variable<br>                    int storage = cb.allocateLocal(TypeKind.REFERENCE);<br>                    cb.aconst_null();<br>                    cb.astore(storage);<br><br>                    // try<br>                    cb.labelBinding(L0);<br>                    cb.loadConstant("Hello!");<br>                    cb.astore(storage);<br>                    cb.labelBinding(L1);<br>                    cb.goto_(L3);<br>                    // catch/merge flow<br>                    cb.labelBinding(L2);<br>                    int e = cb.allocateLocal(TypeKind.REFERENCE);<br>                    cb.astore(e); // exception<br>                    cb.labelBinding(L3);<br>                    cb.return_();<br>                    // register catch<br>                    cb.with(ExceptionCatch.of(L2, L0, L1));<br>                });<br>            });<br>        });<br>        // print the method<br>        System.out.println(cf.parse(bytes).methods().get(0).toDebugString());<br><br>        // print the stack map info<br>        <br>System.out.println(cf.parse(bytes).methods().get(0).code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow().entries().stream().map(Object::toString).collect(Collectors.joining("\n")));<br>        cf.verify(bytes);<br>        ClassLoader cl = new ClassLoader() {<br>            protected Class<?> findClass(final String name) throws ClassNotFoundException {<br>                return name.equals("com.acme.FailVerify") ? defineClass(name, bytes, 0, bytes.length) : super.findClass(name);<br>            }<br>        };<br>        Class<?> defined = cl.loadClass("com.acme.FailVerify");<br>        defined.getMethod("explode", String.class).invoke(null, "Hello world");<br>    }<br>}<br>```<br></div><br></div><div><br></div><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr">- DML • he/him<br></div></div></div>
</blockquote></div><div><br clear="all"></div><div><br></div><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature"><div dir="ltr">- DML • he/him<br></div></div>