<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<div dir="ltr" style="font-family: Aptos, Arial, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">
Hello David,</div>
<div dir="ltr" style="font-family: Aptos, Arial, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">
According to my knowledge is this correct behavior.</div>
<div dir="ltr" style="color: rgb(0, 0, 0);"><span style="font-family: Aptos, Arial, Helvetica, sans-serif; font-size: 12pt;">The stack map frame immediately following the
</span><span style="font-family: arial, helvetica, sans-serif; font-size: 16px; background-color: rgb(255, 255, 255);">4: ASTORE_1 is effectively out of the try/catch block, so it does not have to be assignable to the exception handler.</span></div>
<div dir="ltr" style="font-family: arial, helvetica, sans-serif; font-size: 16px; color: rgb(0, 0, 0);">
<span style="background-color: rgb(255, 255, 255);">By adding <code>cb.aload(storage); cb.astore(storage);</code> or just
<code>cb.nop()</code>; you make it internal part of the try/catch block and the handler frame is adjusted accordingly.</span></div>
<div dir="ltr" style="font-family: arial, helvetica, sans-serif; font-size: 16px; color: rgb(0, 0, 0);">
<span style="background-color: rgb(255, 255, 255);"><br>
</span></div>
<div dir="ltr" style="font-family: arial, helvetica, sans-serif; font-size: 16px; color: rgb(0, 0, 0);">
<span style="background-color: rgb(255, 255, 255);">Yours,</span></div>
<div dir="ltr" style="font-family: arial, helvetica, sans-serif; font-size: 16px; color: rgb(0, 0, 0);">
<span style="background-color: rgb(255, 255, 255);">Adam Sotona</span></div>
<div dir="ltr" style="font-family: Aptos, Arial, Helvetica, sans-serif; font-size: 12pt; color: rgb(0, 0, 0);">
<br>
</div>
<div id="mail-editor-reference-message-container">
<div dir="ltr" class="ms-outlook-mobile-reference-message skipProofing"></div>
<div class="ms-outlook-mobile-reference-message skipProofing" style="text-align: left; padding: 3pt 0in 0in; border-width: 1pt medium medium; border-style: solid none none; border-color: rgb(181, 196, 223) currentcolor currentcolor; font-family: Aptos; font-size: 12pt; color: black;">
<b><br>
<p style="font-family:Calibri;font-size:10pt;color:#000000;margin:5pt;font-style:normal;font-weight:normal;text-decoration:none;" align="Left">
Confidential – Oracle Internal<br>
</p>
From: </b>classfile-api-dev <classfile-api-dev-retn@openjdk.org> on behalf of David Lloyd <david.lloyd@redhat.com><br>
<b>Date: </b>Wednesday, 5 November 2025 at 15:33<br>
<b>To: </b>classfile-api-dev@openjdk.org <classfile-api-dev@openjdk.org><br>
<b>Subject: </b>Re: Possible bug in stack map gen with exceptions<br>
<br>
</div>
<div dir="ltr" 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 dir="ltr" class="ms-outlook-mobile-reference-message skipProofing"><br>
</div>
<div dir="ltr" class="gmail_attr">On Wed, Nov 5, 2025 at 8:08 AM David Lloyd <<a href="mailto:david.lloyd@redhat.com" data-outlook-id="6589cbec-4d5c-405a-95ff-fe1ec3a24643">david.lloyd@redhat.com</a>> wrote:</div>
<blockquote style="margin: 0px 0px 0px 0.8ex; padding-left: 1ex; border-left-width: 1px; border-left-style: solid; border-left-color: rgb(204, 204, 204);">
<div dir="ltr" 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>
<div dir="ltr" class="gmail_quote"><br>
</div>
<div dir="ltr" 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 dir="ltr" class="gmail_default" style="font-family: arial, helvetica, sans-serif;">
<br>
</div>
<div dir="ltr" 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 dir="ltr" class="gmail_default" style="font-family: arial, helvetica, sans-serif;">
<br>
</div>
<div dir="ltr" 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>
```</div>
<div dir="ltr" class="gmail_quote"><br>
</div>
<div dir="ltr" class="gmail_quote"><br>
</div>
<div dir="ltr" class="gmail_quote">--</div>
<div dir="ltr" class="gmail_signature">- DML • he/him</div>
</blockquote>
<div dir="ltr" class="ms-outlook-mobile-reference-message skipProofing"><br>
</div>
<div dir="ltr" class="ms-outlook-mobile-reference-message skipProofing"><br>
</div>
<div class="ms-outlook-mobile-reference-message skipProofing">--</div>
<div dir="ltr" class="gmail_signature">- DML • he/him</div>
</div>
</body>
</html>