<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>