RFR: 8358801: javac produces class that does not pass verifier.
Jan Lahoda
jlahoda at openjdk.org
Tue Jun 17 18:53:28 UTC 2025
On Tue, 17 Jun 2025 15:57:20 GMT, Chen Liang <liach at openjdk.org> wrote:
>> Consider code like:
>>
>>
>> public class Main {
>>
>> private boolean test(String s, int i) {
>> if (s.subSequence(0, 1) instanceof Runnable r) {
>> return true;
>> }
>>
>> Integer dummy;
>> switch (i) {
>> case 0:
>> String clashing = null;
>> return true;
>> default:
>> return true;
>> }
>> }
>>
>> public static void main(String[] args) {
>> }
>> }
>>
>>
>> javac will produce code that won't (rightfully) pass the verifier:
>>
>>
>> $ java Main.java
>> Exception in thread "main" java.lang.VerifyError: Inconsistent stackmap frames at branch target 49
>> Exception Details:
>> Location:
>> Main.test(Ljava/lang/String;I)Z @49: iconst_1
>> Reason:
>> Type top (current frame, locals[4]) is not assignable to 'java/lang/String' (stack map, locals[4])
>> Current Frame:
>> bci: @25
>> flags: { }
>> locals: { 'Main', 'java/lang/String', integer }
>> stack: { integer }
>> Stackmap Frame:
>> bci: @49
>> flags: { }
>> locals: { 'Main', 'java/lang/String', integer, top, 'java/lang/String' }
>> stack: { }
>> Bytecode:
>> 0000000: 2b03 04b6 0007 3a04 1904 c100 0d99 000b
>> 0000010: 1904 c000 0d4e 04ac 1cab 0000 0000 0018
>> 0000020: 0000 0001 0000 0000 0000 0013 013a 0404
>> 0000030: ac04 ac
>> Stackmap Table:
>> same_frame(@24)
>> same_frame(@44)
>> append_frame(@49,Top,Object[#8])
>>
>> at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
>> at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3035)
>> at java.base/java.lang.Class.getMethodsRecursive(Class.java:3177)
>> at java.base/java.lang.Class.findMethod(Class.java:2465)
>> at java.base/java.lang.System$1.findMethod(System.java:1980)
>> at java.base/jdk.internal.misc.MethodFinder.findMainMethod(MethodFinder.java:86)
>> at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.execute(SourceLauncher.java:194)
>> at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.run(SourceLauncher.java:138)
>> at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.main(SourceLauncher.java:76)
>>
>>
>>
>> Now, the problem, as far as I can tell, is this: javac will desugar the pattern matching instanceof along the lines of:
>>
>>
>> if (... (var $temp = s.subSequence(0, 1) in ... && ...) ...) {
>> return true;
>> }
>>
>>
>> (`$temp` is register/local variable...
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java line 763:
>
>> 761: //expression:
>> 762: undefineVariablesInChain(result.falseJumps, limit);
>> 763: undefineVariablesInChain(result.trueJumps, limit);
>
> Should we move this truncation of variables to CondItem itself? I moved the truncation of continue/break to GenContext and found that helpful, maybe we can require an extra int arg for `makeCondItem(int, Chain, Chain)` for the limit?
To me, personally: while doing it here is not perfect (mostly because I would prefer if we didn't have to do this bookkeeping at all), it seems to me like a fairly local property - the let expr starts, and then does its own cleanup. It already calls `code.endScopes(limit);` above this change, so this is basically an extension to that. If we did it at `makeCondItem` time, we would need to pass the limit everywhere `makeCondItem` is called, making it non-local. Many parts of the code would now suddenly have to care whether the node is inside a let expression. That feels to me like increasing complexity.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/25849#discussion_r2152954768
More information about the compiler-dev
mailing list