RFR: 8370175: State engine terminates when throwing self-caused exception
Pavel Rappo
prappo at openjdk.org
Wed Oct 29 10:06:23 UTC 2025
On Wed, 29 Oct 2025 06:51:12 GMT, Christian Stein <cstein at openjdk.org> wrote:
> Please review this change to prevent an engine termination in `jshell` in a self-causation exception scenario.
>
> Prior to the fix, a non-null cause exception was unconditionally converted and used. Now, a set with identity equalitiy semantics is guarding the process, by breaking a recursive chains when a reference cycle is detected. The guard implementation is inspired by the one used in [java.base/java.lang.Throwable](https://github.com/openjdk/jdk/blob/0687f120cc324f35fe43d811b6beb4184fd854ec/src/java.base/share/classes/java/lang/Throwable.java#L689-L693)
>
> Note that a direct self-causation state is already prevented at the `Throwable::initCause` method level:
>
>
> jshell> var t = new Throwable()
> t ==> java.lang.Throwable
>
> jshell> t.initCause(t)
> | Exception java.lang.IllegalArgumentException: Self-causation not permitted
> | at Throwable.initCause (Throwable.java:492)
> | at (#8:1)
> | Caused by: java.lang.Throwable
> | at do_it$Aux (#7:1)
> | at (#7:1)
As a reporter, thanks for taking care of this. One note is that Throwable.printStackTrace also takes care of cycles in _suppressed_ trees. JShell does not seem to print suppressed exceptions, but if it ever does, we will need to treat them the same way.
jshell> var e1 = new Error();
...> var e2 = new Error();
...> e1.addSuppressed(e2);
...> e2.addSuppressed(e1);
...> e1.printStackTrace();
e1 ==> java.lang.Error
e2 ==> java.lang.Error
java.lang.Error
at REPL.$JShell$2.do_it$Aux($JShell$2.java:6)
at REPL.$JShell$2.do_it$($JShell$2.java:11)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at jdk.jshell/jdk.jshell.execution.DirectExecutionControl.invoke(DirectExecutionControl.java:227)
at jdk.jshell/jdk.jshell.execution.RemoteExecutionControl.invoke(RemoteExecutionControl.java:121)
at jdk.jshell/jdk.jshell.execution.DirectExecutionControl.invoke(DirectExecutionControl.java:125)
at jdk.jshell/jdk.jshell.execution.ExecutionControlForwarder.processCommand(ExecutionControlForwarder.java:148)
at jdk.jshell/jdk.jshell.execution.ExecutionControlForwarder.commandLoop(ExecutionControlForwarder.java:266)
at jdk.jshell/jdk.jshell.execution.Util.forwardExecutionControl(Util.java:78)
at jdk.jshell/jdk.jshell.execution.Util.forwardExecutionControlAndIO(Util.java:148)
at jdk.jshell/jdk.jshell.execution.RemoteExecutionControl.main(RemoteExecutionControl.java:74)
Suppressed: java.lang.Error
at REPL.$JShell$3.do_it$Aux($JShell$3.java:6)
at REPL.$JShell$3.do_it$($JShell$3.java:11)
... 10 more
Suppressed: [CIRCULAR REFERENCE: java.lang.Error]
jshell> throw e1
| Exception java.lang.Error
| at do_it$Aux (#1:1)
| at (#1:1)
Btw, why doesn't JShell print suppressed exceptions? Seems helpful in try-with-resources blocks.
-------------
PR Comment: https://git.openjdk.org/jdk/pull/28037#issuecomment-3460688628
More information about the compiler-dev
mailing list