RFR: 8353565: Javac throws "inconsistent stack types at join point" exception
Jan Lahoda
jlahoda at openjdk.org
Mon Apr 14 12:53:45 UTC 2025
Consider code like:
public class T {
private Object computeTypeAtMergePoint1(int i) {
return (Object) switch (i) {
case 0 -> switch (i) {
case 0 -> { yield new A(); }
default -> { yield new B(); }
};
default -> switch (i) {
case 0 -> { yield new C(); }
default -> { yield new D(); }
};
};
}
enum E {A, B}
interface I1 {}
class A implements I1 {}
class B implements I1 {}
interface I2 {}
class C implements I2 {}
class D implements I2 {}
}
Compiling this leads to a crash:
$ .../jdk-24/bin/javac /tmp/T.java
An exception has occurred in the compiler (24-internal). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.
java.lang.AssertionError: inconsistent stack types at join point
at jdk.compiler/com.sun.tools.javac.jvm.Code$State.error(Code.java:1824)
at jdk.compiler/com.sun.tools.javac.jvm.Code$State.join(Code.java:1814)
at jdk.compiler/com.sun.tools.javac.jvm.Code.resolve(Code.java:1525)
at jdk.compiler/com.sun.tools.javac.jvm.Code.resolvePending(Code.java:1556)
at jdk.compiler/com.sun.tools.javac.jvm.Code.emitop(Code.java:382)
at jdk.compiler/com.sun.tools.javac.jvm.Code.emitop0(Code.java:511)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitReturn(Gen.java:1905)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCReturn.accept(JCTree.java:1773)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:588)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:623)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:609)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStats(Gen.java:660)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.internalVisitBlock(Gen.java:1121)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitBlock(Gen.java:1085)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1137)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:588)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:623)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genMethod(Gen.java:949)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitMethodDef(Gen.java:912)
at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCMethodDecl.accept(JCTree.java:961)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:588)
at jdk.compiler/com.sun.tools.javac.jvm.Gen.genClass(Gen.java:2494)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.genCode(JavaCompiler.java:771)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1710)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.generate(JavaCompiler.java:1678)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:978)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:319)
at jdk.compiler/com.sun.tools.javac.main.Main.compile(Main.java:178)
at jdk.compiler/com.sun.tools.javac.Main.compile(Main.java:66)
at jdk.compiler/com.sun.tools.javac.Main.main(Main.java:52)
printing javac parameters to: /tmp/javac.20250414_101233.args
The reason is that all of the yields "jump" to the same point in the bytecode, and javac is unable to compute the top-of-stack type for that point. javac expects that there's subtyping relation for the types on the join point.
In a little bit more detail:
- the `(Object)` cast is important, as that makes the switch expressions standalone expressions, whose types are inferred from the content, not from the target type
-if we had only:
private Object computeTypeAtMergePoint1(int i) {
return (Object) switch (i) {
case 0 -> { yield new A(); }
default -> { yield new D(); }
};
}
then this would compile, as the type of the switch expression would be `Object`, and inside `visitYield`, we use `forceStackTop` to change the top-stack type to the type of the switch expression. This works on one level, but does not work well for nested level switch expressions - while the outer switch expression in the original example has type `Object`, the nested ones have `I1` and `I2`, respectively, and the nested `yield` statements set `I1` or `I2` as the stack-top type. And neither of `I1` or `I2` is a subtype of the other, leading to the failure.
The proposal here is to use `Types.lub` to compute the common supertype of the types that join. The subtyping checks are kept, to help with some corner cases, in particular with `null`/`BOT` type handling. And also to limit potential performance impact.
I also think the `forceStackTop` is more a workaround that permitted the use of simply subtyping rather than `lub` at the join points. And it only works for one level of nesting for switch expressions. The proposal here is to drop the `forceStackTop` for both switch expressions and conditional expressions, so that if there are some cases where the new code would not work, we would find out faster/easier. But I can keep the `forceStackTop` calls in that is preferred.
(In any case, there's one call to `forceStackTop` in `Gen.visitAssign`. That one seems legitimate to me, so that one is kept.)
-------------
Commit messages:
- 8353565: Javac throws "inconsistent stack types at join point" exception
Changes: https://git.openjdk.org/jdk/pull/24617/files
Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=24617&range=00
Issue: https://bugs.openjdk.org/browse/JDK-8353565
Stats: 122 lines in 3 files changed: 112 ins; 3 del; 7 mod
Patch: https://git.openjdk.org/jdk/pull/24617.diff
Fetch: git fetch https://git.openjdk.org/jdk.git pull/24617/head:pull/24617
PR: https://git.openjdk.org/jdk/pull/24617
More information about the compiler-dev
mailing list