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