Skip to content

Conversation

lahodaj
Copy link
Contributor

@lahodaj lahodaj commented Oct 7, 2025

Consider a JShell interaction like:

jshell> class O { class I {} }
|  created class O

jshell> var i = new O().new I();
i ==> O$I@77caeb3e

jshell> class O { static class I {} }
Exception in thread "main" java.lang.ClassFormatError: class not in class file format
        at jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.redefineClasses(VirtualMachineImpl.java:396)
        at jdk.jshell/jdk.jshell.execution.JdiExecutionControl.redefine(JdiExecutionControl.java:90)
        at jdk.jshell/jdk.jshell.Unit.doRedefines(Unit.java:312)
        at jdk.jshell/jdk.jshell.Eval.lambda$compileAndLoad$27(Eval.java:1120)
        at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178)
        at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:722)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
        at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
        at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
        at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)
        at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)
        at jdk.jshell/jdk.jshell.Eval.lambda$compileAndLoad$29(Eval.java:1121)
        at jdk.jshell/jdk.jshell.TaskFactory.lambda$runTask$4(TaskFactory.java:213)
        at jdk.compiler/com.sun.tools.javac.api.JavacTaskPool.getTask(JavacTaskPool.java:193)
        at jdk.jshell/jdk.jshell.TaskFactory.runTask(TaskFactory.java:206)
        at jdk.jshell/jdk.jshell.TaskFactory.compile(TaskFactory.java:186)
        at jdk.jshell/jdk.jshell.Eval.compileAndLoad(Eval.java:1100)
        at jdk.jshell/jdk.jshell.Eval.declare(Eval.java:901)
        at jdk.jshell/jdk.jshell.Eval.eval(Eval.java:140)
        at jdk.jshell/jdk.jshell.JShell.eval(JShell.java:513)
        at jdk.jshell/jdk.internal.jshell.tool.JShellTool.processSource(JShellTool.java:3633)
        at jdk.jshell/jdk.internal.jshell.tool.JShellTool.processSourceCatchingReset(JShellTool.java:1353)
        at jdk.jshell/jdk.internal.jshell.tool.JShellTool.processInput(JShellTool.java:1251)
        at jdk.jshell/jdk.internal.jshell.tool.JShellTool.run(JShellTool.java:1222)
        at jdk.jshell/jdk.internal.jshell.tool.JShellTool.start(JShellTool.java:1005)
        at jdk.jshell/jdk.internal.jshell.tool.JShellToolBuilder.start(JShellToolBuilder.java:261)
        at jdk.jshell/jdk.internal.jshell.tool.JShellToolProvider.main(JShellToolProvider.java:120)

There are two problems here (although the stack trace immediately only shows one of them):

Redefining Classes

When a snippet is redefined, JShell first tries to redefine it using JDI (VirtualMachine.redefineClasses). This usually throws UnsupportedOperationException is the redefine cannot happen, and JdiExecutionControl handles that gracefully. JShell will recompile and reload the given snippet, and dependent snippets, under different names.

But the redefineClasses method can also throw various LinkageErrors. These are properly documented for the method: https://docs.oracle.com/en/java/javase/25/docs/api/jdk.jdi/com/sun/jdi/VirtualMachine.html#redefineClasses(java.util.Map)

But JdiExecutionControl is not handling these LinkageErrors.

The proposed solution herein is to simply catch and handle the LinkageErrors in the same way as the UnsupportedOperationException (and other exceptions).

InnerClasses attribute data overriding information from sources

Consider situation when compiling the third input: class O { static class I {} }.

When this is being compiled, a classfile for var i = new O().new I(); exists, and its InnerClasses attribute records O.I to be a non-static inner class.

While compiling the code for class O { static class I {} }, the classfile for var i is also read from the classfile, and its InnerClasses attribute is read as well. And as a consequence, the O.I class will be marked as non-static, which conflicts with what is in the source file.

This is not related to JShell as such, it can be reproduced with javac. Please see the test/langtools/tools/javac/recovery/SourceAndInnerClassInconsistency.java test.

The proposal herein is to not use the information from the InnerClasses classfile attribute to manipulate classes that originate in a source file. More generally, I think the information from the source should always prevail over information from other/unrelated classfiles. Note the InnerClasses attribute is in a classfile that has no real relation to the source code that is being compiled, it is simply an classfile on the classpath.

/issue JDK-8368999


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

Warning

 ⚠️ Found leading lowercase letter in issue title for 8340840: jshell ClassFormatError when making inner class static, 8368999: jshell crash when existing sealed class is updated to also be abstract

Issues

  • JDK-8340840: jshell ClassFormatError when making inner class static (Bug - P4)
  • JDK-8368999: jshell crash when existing sealed class is updated to also be abstract (Bug - P4)

Reviewers

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 27665

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

Using diff file

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

Using Webrev

Link to Webrev Comment

8368999: jshell crash when existing sealed class is updated to also be abstract
@bridgekeeper
Copy link

bridgekeeper bot commented Oct 7, 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 Oct 7, 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:

8340840: jshell ClassFormatError when making inner class static
8368999: jshell crash when existing sealed class is updated to also be abstract

Reviewed-by: 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 195 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
Copy link

openjdk bot commented Oct 7, 2025

@lahodaj
Adding additional issue to issue list: 8368999: jshell crash when existing sealed class is updated to also be abstract.

@openjdk
Copy link

openjdk bot commented Oct 7, 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 rfr Pull request is ready for review label Oct 7, 2025
@mlbridge
Copy link

mlbridge bot commented Oct 7, 2025

Webrevs

} catch (EngineTerminationException ex) {
throw ex;
} catch (Exception ex) {
} catch (Exception | LinkageError ex) {
Copy link
Member

Choose a reason for hiding this comment

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

How was ClassFormatError previously thrown here? Was it wrapped in some exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was simply thrown out of the redefine method, as the stacktrace in the summary suggests. Note that the javadoc for redefineClasses is very clear it may throw ClassFormatError:
https://docs.oracle.com/en/java/javase/25/docs/api/jdk.jdi/com/sun/jdi/VirtualMachine.html#redefineClasses(java.util.Map)

if (c == outer && member.owner == c) {
member.flags_field = flags;
enterMember(c, member);
if ((member.flags_field & FROM_SOURCE) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with the fix -- I guess I'd slightly prefer to see a warning if a mismatch is detected, but I'll leave that to you.

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.

javac changes look good (did not look at the jshell part)

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler [email protected] ready Pull request is ready to be integrated rfr Pull request is ready for review

Development

Successfully merging this pull request may close these issues.

3 participants