Skip to content

Conversation

@lahodaj
Copy link
Contributor

@lahodaj lahodaj commented Apr 14, 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.)


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8353565: Javac throws "inconsistent stack types at join point" exception (Bug - P3)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24617/head:pull/24617
$ git checkout pull/24617

Update a local copy of the PR:
$ git checkout pull/24617
$ git pull https://git.openjdk.org/jdk.git pull/24617/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 24617

View PR using the GUI difftool:
$ git pr show -t 24617

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24617.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Apr 14, 2025

👋 Welcome back jlahoda! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Apr 14, 2025

@lahodaj This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8353565: Javac throws "inconsistent stack types at join point" exception

Reviewed-by: vromero, liach, mcimadamore

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 362 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the rfr Pull request is ready for review label Apr 14, 2025
@openjdk
Copy link

openjdk bot commented Apr 14, 2025

@lahodaj The following label will be automatically applied to this pull request:

  • compiler

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the compiler compiler-dev@openjdk.org label Apr 14, 2025
@mlbridge
Copy link

mlbridge bot commented Apr 14, 2025

Webrevs

Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Apr 14, 2025
types.isSubtype(t, tother) ? tother :
types.isSubtype(tother, t) ? t :
error();
commonSuperClass(t, tother);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the isSubtype() or t==tother checks above still necessary? Like do those fast paths not exist in types::lub?

Copy link
Member

@liach liach Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked, lub is a varargs call and is slow. However, I think the fast path merge logic t==tother? ... should be in commonSuperClass.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isSubtype serve two purposes - they handle null/BOT type (which, I think, lub normally does not handle), and they are cheaper to run.

I've moved them into commonSuperClass, as suggested, here:
bb08b49

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Apr 15, 2025
Copy link
Contributor

@vicente-romero-oracle vicente-romero-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lg

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Apr 15, 2025
Copy link
Member

@liach liach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating per my review!

Copy link
Contributor

@mcimadamore mcimadamore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a nice generalization of what the code used to do. One possible problematic aspect is non-denotable types. E.g. if A and B both implements two interfaces like I1 and I2. In that case the lub will be I1 & I2 which erased will lead you to I1. I think this is still correct because the verifier doesn't concern with interface types -- but would probably be better to double check.

Another alternative would be to use Object as the stackmap type is the join is non-denotable. But then I'm not sure what would be the ramifications of doing that. Using the erasure seems safer because javac will insert synthetic cast if e.g. accessing a member on something that doesn't look like the erasure (e.g. calling a method in I2 on a I1 & I2).

Copy link
Contributor

@mcimadamore mcimadamore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second look, the test for intersection type is already there. No further comments from me!

@lahodaj
Copy link
Contributor Author

lahodaj commented May 5, 2025

/integrate

@openjdk
Copy link

openjdk bot commented May 5, 2025

Going to push as commit a5f4366.
Since your change was applied there have been 402 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label May 5, 2025
@openjdk openjdk bot closed this May 5, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels May 5, 2025
@openjdk
Copy link

openjdk bot commented May 5, 2025

@lahodaj Pushed as commit a5f4366.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler compiler-dev@openjdk.org integrated Pull request has been integrated

Development

Successfully merging this pull request may close these issues.

4 participants