Skip to content

Commit f1e06c3

Browse files
committed
8299934: LocalExecutionControl replaces default uncaught exception handler
Reviewed-by: liach
1 parent 6dd5553 commit f1e06c3

File tree

2 files changed

+169
-14
lines changed

2 files changed

+169
-14
lines changed

src/jdk.jshell/share/classes/jdk/jshell/execution/LocalExecutionControl.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,28 +132,29 @@ protected String invoke(Method doitMethod) throws Exception {
132132
}
133133
allStop.set(null, false);
134134

135-
execThreadGroup = new ThreadGroup("JShell process local execution");
136-
137135
AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>();
138136
AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>();
139137
AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>();
140138
AtomicReference<Boolean> stopped = new AtomicReference<>(false);
141139

142-
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
143-
if (e instanceof InvocationTargetException) {
144-
if (e.getCause() instanceof ThreadDeath) {
140+
execThreadGroup = new ThreadGroup("JShell process local execution") {
141+
@Override
142+
public void uncaughtException(Thread t, Throwable e) {
143+
if (e instanceof InvocationTargetException) {
144+
if (e.getCause() instanceof ThreadDeath) {
145+
stopped.set(true);
146+
} else {
147+
iteEx.set((InvocationTargetException) e);
148+
}
149+
} else if (e instanceof IllegalAccessException) {
150+
iaeEx.set((IllegalAccessException) e);
151+
} else if (e instanceof NoSuchMethodException) {
152+
nmeEx.set((NoSuchMethodException) e);
153+
} else if (e instanceof ThreadDeath) {
145154
stopped.set(true);
146-
} else {
147-
iteEx.set((InvocationTargetException) e);
148155
}
149-
} else if (e instanceof IllegalAccessException) {
150-
iaeEx.set((IllegalAccessException) e);
151-
} else if (e instanceof NoSuchMethodException) {
152-
nmeEx.set((NoSuchMethodException) e);
153-
} else if (e instanceof ThreadDeath) {
154-
stopped.set(true);
155156
}
156-
});
157+
};
157158

158159
final Object[] res = new Object[1];
159160
Thread snippetThread = new Thread(execThreadGroup, () -> {
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8299934
27+
* @summary Test LocalExecutionControl
28+
* @run junit LocalExecutionControlExceptionTest
29+
*/
30+
31+
import java.io.ByteArrayOutputStream;
32+
import java.io.IOException;
33+
import java.io.PrintStream;
34+
import java.lang.reflect.Method;
35+
import java.util.ArrayList;
36+
import java.util.List;
37+
import java.util.Map;
38+
import jdk.jshell.EvalException;
39+
import jdk.jshell.JShell;
40+
import jdk.jshell.execution.LocalExecutionControlProvider;
41+
import org.junit.jupiter.api.*;
42+
import org.junit.jupiter.api.extension.*;
43+
44+
public class LocalExecutionControlExceptionTest {
45+
46+
@Test
47+
@ExtendWith(NoUncaughtExceptionHandleInterceptor.class)
48+
public void testUncaughtExceptions() throws InterruptedException {
49+
List<Throwable> excs = new ArrayList<>();
50+
ByteArrayOutputStream out = new ByteArrayOutputStream();
51+
Thread.setDefaultUncaughtExceptionHandler((t, ex) -> excs.add(ex));
52+
try (var jshell = JShell.builder()
53+
.err(new PrintStream(out))
54+
.out(new PrintStream(out))
55+
.executionEngine(new LocalExecutionControlProvider(), Map.of())
56+
.build()) {
57+
{
58+
var events = jshell.eval("throw new java.io.IOException();");
59+
Assertions.assertEquals(1, events.size());
60+
Assertions.assertNotNull(events.get(0).exception());
61+
Assertions.assertTrue(events.get(0).exception() instanceof EvalException);
62+
Assertions.assertEquals("java.io.IOException",
63+
((EvalException) events.get(0).exception()).getExceptionClassName());
64+
Assertions.assertEquals(0, excs.size());
65+
Assertions.assertEquals(0, out.size());
66+
}
67+
{
68+
var events = jshell.eval("throw new IllegalAccessException();");
69+
Assertions.assertEquals(1, events.size());
70+
Assertions.assertNotNull(events.get(0).exception());
71+
Assertions.assertTrue(events.get(0).exception() instanceof EvalException);
72+
Assertions.assertEquals("java.lang.IllegalAccessException",
73+
((EvalException) events.get(0).exception()).getExceptionClassName());
74+
Assertions.assertEquals(0, excs.size());
75+
Assertions.assertEquals(0, out.size());
76+
}
77+
jshell.eval("""
78+
<T extends Throwable> T t2(Throwable t) throws T {
79+
throw (T) t;
80+
}
81+
""");
82+
jshell.eval("""
83+
void t(Throwable t) {
84+
throw t2(t);
85+
}
86+
""");
87+
{
88+
var events = jshell.eval("""
89+
{
90+
var t = new Thread(() -> t(new java.io.IOException()));
91+
t.start();
92+
t.join();
93+
}
94+
""");
95+
Assertions.assertEquals(1, events.size());
96+
Assertions.assertNull(events.get(0).exception());
97+
Assertions.assertEquals(0, excs.size());
98+
Assertions.assertEquals(0, out.size());
99+
}
100+
{
101+
var events = jshell.eval("""
102+
{
103+
var t = new Thread(() -> t(new IllegalAccessException()));
104+
t.start();
105+
t.join();
106+
}
107+
""");
108+
Assertions.assertEquals(1, events.size());
109+
Assertions.assertNull(events.get(0).exception());
110+
Assertions.assertEquals(0, excs.size());
111+
Assertions.assertEquals(0, out.size());
112+
}
113+
Thread outsideOfJShell = new Thread(() -> {
114+
t(new IOException());
115+
});
116+
outsideOfJShell.start();
117+
outsideOfJShell.join();
118+
Assertions.assertEquals(1, excs.size());
119+
}
120+
}
121+
122+
void t(Throwable t) {
123+
throw t2(t);
124+
}
125+
126+
<T extends Throwable> T t2(Throwable t) throws T {
127+
throw (T) t;
128+
}
129+
130+
public static final class NoUncaughtExceptionHandleInterceptor implements InvocationInterceptor {
131+
public void interceptTestMethod(Invocation<Void> invocation,
132+
ReflectiveInvocationContext<Method> invocationContext,
133+
ExtensionContext extensionContext) throws Throwable {
134+
Throwable[] exc = new Throwable[1];
135+
//the tests normally run in a ThreadGroup which handles uncaught exception
136+
//run in a ThreadGroup which does not handle the uncaught exceptions, and let them
137+
//pass to the default uncaught handler for the test:
138+
var thread = new Thread(Thread.currentThread().getThreadGroup().getParent(), "test-group") {
139+
public void run() {
140+
try {
141+
invocation.proceed();
142+
} catch (Throwable ex) {
143+
exc[0] = ex;
144+
}
145+
}
146+
};
147+
thread.start();
148+
thread.join();
149+
if (exc[0] != null) {
150+
throw exc[0];
151+
}
152+
}
153+
}
154+
}

0 commit comments

Comments
 (0)