Skip to content

Commit f60d396

Browse files
parttimenerdPaul Hohensee
authored and
Paul Hohensee
committed
8317920: JDWP-agent sends broken exception event with onthrow option
Reviewed-by: phh
1 parent fa30733 commit f60d396

File tree

4 files changed

+255
-21
lines changed

4 files changed

+255
-21
lines changed

src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c

+27-17
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ static void JNICALL cbEarlyVMDeath(jvmtiEnv*, JNIEnv *);
102102
static void JNICALL cbEarlyException(jvmtiEnv*, JNIEnv *,
103103
jthread, jmethodID, jlocation, jobject, jmethodID, jlocation);
104104

105-
static void initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei);
105+
static void initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei, EventInfo *opt_info);
106106
static jboolean parseOptions(char *str);
107107

108108
/*
@@ -438,7 +438,7 @@ cbEarlyVMInit(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thread)
438438
EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM dead at VM_INIT time");
439439
}
440440
if (initOnStartup)
441-
initialize(env, thread, EI_VM_INIT);
441+
initialize(env, thread, EI_VM_INIT, NULL);
442442
vmInitialized = JNI_TRUE;
443443
LOG_MISC(("END cbEarlyVMInit"));
444444
}
@@ -491,6 +491,16 @@ cbEarlyException(jvmtiEnv *jvmti_env, JNIEnv *env,
491491
LOG_MISC(("VM is not initialized yet"));
492492
return;
493493
}
494+
EventInfo info;
495+
info.ei = EI_EXCEPTION;
496+
info.thread = thread;
497+
info.clazz = getMethodClass(jvmti_env, method);
498+
info.method = method;
499+
info.location = location;
500+
info.object = exception;
501+
info.u.exception.catch_clazz = getMethodClass(jvmti_env, catch_method);
502+
info.u.exception.catch_method = catch_method;
503+
info.u.exception.catch_location = catch_location;
494504

495505
/*
496506
* We want to preserve any current exception that might get wiped
@@ -505,24 +515,22 @@ cbEarlyException(jvmtiEnv *jvmti_env, JNIEnv *env,
505515
if (initOnUncaught && catch_method == NULL) {
506516

507517
LOG_MISC(("Initializing on uncaught exception"));
508-
initialize(env, thread, EI_EXCEPTION);
518+
initialize(env, thread, EI_EXCEPTION, &info);
509519

510520
} else if (initOnException != NULL) {
511521

512-
jclass clazz;
513-
514-
/* Get class of exception thrown */
515-
clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, exception);
516-
if ( clazz != NULL ) {
522+
jclass exception_clazz = JNI_FUNC_PTR(env, GetObjectClass)(env, exception);
523+
/* check class of exception thrown */
524+
if ( exception_clazz != NULL ) {
517525
char *signature = NULL;
518526
/* initing on throw, check */
519-
error = classSignature(clazz, &signature, NULL);
527+
error = classSignature(exception_clazz, &signature, NULL);
520528
LOG_MISC(("Checking specific exception: looking for %s, got %s",
521529
initOnException, signature));
522530
if ( (error==JVMTI_ERROR_NONE) &&
523531
(strcmp(signature, initOnException) == 0)) {
524532
LOG_MISC(("Initializing on specific exception"));
525-
initialize(env, thread, EI_EXCEPTION);
533+
initialize(env, thread, EI_EXCEPTION, &info);
526534
} else {
527535
error = AGENT_ERROR_INTERNAL; /* Just to cause restore */
528536
}
@@ -663,9 +671,11 @@ jniFatalError(JNIEnv *env, const char *msg, jvmtiError error, int exit_code)
663671

664672
/*
665673
* Initialize debugger back end modules
674+
*
675+
* @param opt_info optional event info to use, might be null
666676
*/
667677
static void
668-
initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei)
678+
initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei, EventInfo *opt_info)
669679
{
670680
jvmtiError error;
671681
EnumerateArg arg;
@@ -753,13 +763,13 @@ initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei)
753763
* can get in the queue (from other not-yet-suspended threads)
754764
* before this one does. (Also need to handle allocation error below?)
755765
*/
756-
EventInfo info;
757766
struct bag *initEventBag;
758-
LOG_MISC(("triggering_ei != EI_VM_INIT"));
767+
LOG_MISC(("triggering_ei == EI_EXCEPTION"));
768+
JDI_ASSERT(triggering_ei == EI_EXCEPTION);
769+
JDI_ASSERT(opt_info != NULL);
759770
initEventBag = eventHelper_createEventBag();
760-
(void)memset(&info,0,sizeof(info));
761-
info.ei = triggering_ei;
762-
eventHelper_recordEvent(&info, 0, suspendPolicy, initEventBag);
771+
threadControl_onEventHandlerEntry(currentSessionID, opt_info, NULL);
772+
eventHelper_recordEvent(opt_info, 0, suspendPolicy, initEventBag);
763773
(void)eventHelper_reportEvents(currentSessionID, initEventBag);
764774
bagDestroyBag(initEventBag);
765775
}
@@ -1394,7 +1404,7 @@ JNIEXPORT char const* JNICALL debugInit_startDebuggingViaCommand(JNIEnv* env, jt
13941404
if (!startedViaJcmd) {
13951405
startedViaJcmd = JNI_TRUE;
13961406
is_first_start = JNI_TRUE;
1397-
initialize(env, thread, EI_VM_INIT);
1407+
initialize(env, thread, EI_VM_INIT, NULL);
13981408
}
13991409

14001410
bagEnumerateOver(transports, getFirstTransport, &spec);
+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright (c) 2023 SAP SE. 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+
import com.sun.jdi.Bootstrap;
25+
import com.sun.jdi.VirtualMachine;
26+
import com.sun.jdi.connect.AttachingConnector;
27+
import com.sun.jdi.connect.Connector;
28+
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
29+
import com.sun.jdi.event.EventIterator;
30+
import com.sun.jdi.event.EventQueue;
31+
import com.sun.jdi.event.EventSet;
32+
import com.sun.jdi.event.Event;
33+
import com.sun.jdi.event.ExceptionEvent;
34+
import lib.jdb.Debuggee;
35+
36+
import java.io.IOException;
37+
import java.net.ServerSocket;
38+
import java.util.Iterator;
39+
import java.util.List;
40+
import java.util.Map;
41+
42+
/*
43+
* @test
44+
* @bug 8317920
45+
* @summary Tests for JDWP agent to send valid exception event with onthrow option
46+
* @library /test/lib
47+
*
48+
* @build ThrowCaughtException JdwpOnThrowTest
49+
* @run main/othervm JdwpOnThrowTest
50+
*/
51+
public class JdwpOnThrowTest {
52+
53+
private static long TIMEOUT = 10000;
54+
55+
private static String ATTACH_CONNECTOR = "com.sun.jdi.SocketAttach";
56+
// cache socket attaching connector
57+
private static AttachingConnector attachingConnector;
58+
59+
public static void main(String[] args) throws Exception {
60+
int port = findFreePort();
61+
try (Debuggee debuggee = Debuggee.launcher("ThrowCaughtException").setAddress("localhost:" + port)
62+
.enableOnThrow("Ex", "Start").setSuspended(true).launch()) {
63+
VirtualMachine vm = null;
64+
try {
65+
vm = attach("localhost", "" + port);
66+
EventQueue queue = vm.eventQueue();
67+
log("Waiting for exception event");
68+
long start = System.currentTimeMillis();
69+
while (start + TIMEOUT > System.currentTimeMillis()) {
70+
EventSet eventSet = queue.remove(TIMEOUT);
71+
EventIterator eventIterator = eventSet.eventIterator();
72+
while(eventIterator.hasNext() && start + TIMEOUT > System.currentTimeMillis()) {
73+
Event event = eventIterator.next();
74+
if (event instanceof ExceptionEvent ex) {
75+
verifyExceptionEvent(ex);
76+
log("Received exception event: " + event);
77+
vm.dispose();
78+
return;
79+
}
80+
log("Received event: " + event);
81+
}
82+
}
83+
throw new RuntimeException("ERROR: failed to receive exception event");
84+
} catch (IOException ex) {
85+
throw new RuntimeException("ERROR: failed to attach", ex);
86+
}
87+
}
88+
}
89+
90+
private static void verifyExceptionEvent(ExceptionEvent ex) throws Exception {
91+
if (ex.exception() == null) {
92+
throw new RuntimeException("Exception is null");
93+
}
94+
if (ex.exception().type() == null) {
95+
throw new RuntimeException("Exception type is null");
96+
}
97+
if (ex.exception().referenceType() == null) {
98+
throw new RuntimeException("Exception reference type is null");
99+
}
100+
if (ex.catchLocation() == null) {
101+
throw new RuntimeException("Exception catch location is null");
102+
}
103+
if (!ex.location().equals(ex.thread().frame(0).location())) {
104+
throw new RuntimeException(
105+
String.format("Throw location %s and location of first frame %s are not equal",
106+
ex.location(), ex.thread().frame(0).location()));
107+
}
108+
if (!ex.exception().type().name().equals("Ex")) {
109+
throw new RuntimeException("Exception has wrong type: " + ex.exception().type().name());
110+
}
111+
}
112+
113+
private static int findFreePort() {
114+
try (ServerSocket socket = new ServerSocket(0)) {
115+
return socket.getLocalPort();
116+
} catch (IOException e) {
117+
throw new RuntimeException(e);
118+
}
119+
}
120+
121+
private static VirtualMachine attach(String address, String port) throws IOException {
122+
if (attachingConnector == null) {
123+
attachingConnector = (AttachingConnector)getConnector(ATTACH_CONNECTOR);
124+
}
125+
Map<String, Connector.Argument> args = attachingConnector.defaultArguments();
126+
setConnectorArg(args, "hostname", address);
127+
setConnectorArg(args, "port", port);
128+
try {
129+
return attachingConnector.attach(args);
130+
} catch (IllegalConnectorArgumentsException e) {
131+
// unexpected.. wrap in RuntimeException
132+
throw new RuntimeException(e);
133+
}
134+
}
135+
136+
private static Connector getConnector(String name) {
137+
for (Connector connector : Bootstrap.virtualMachineManager().allConnectors()) {
138+
if (connector.name().equalsIgnoreCase(name)) {
139+
return connector;
140+
}
141+
}
142+
throw new IllegalArgumentException("Connector " + name + " not found");
143+
}
144+
145+
private static void setConnectorArg(Map<String, Connector.Argument> args, String name, String value) {
146+
Connector.Argument arg = args.get(name);
147+
if (arg == null) {
148+
throw new IllegalArgumentException("Argument " + name + " is not defined");
149+
}
150+
arg.setValue(value);
151+
}
152+
153+
private static void log(Object o) {
154+
System.out.println(String.valueOf(o));
155+
}
156+
157+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2023 SAP SE. 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+
public class ThrowCaughtException {
25+
public static void main(String args[]) throws Exception {
26+
try {
27+
System.out.println("Start");
28+
throw new Ex();
29+
} catch (Exception e) {
30+
System.out.println(e);
31+
}
32+
}
33+
}
34+
35+
class Ex extends RuntimeException {
36+
}

test/jdk/com/sun/jdi/lib/jdb/Debuggee.java

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -69,6 +69,9 @@ public static class Launcher {
6969
private String transport = "dt_socket";
7070
private String address = null;
7171
private boolean suspended = true;
72+
private String onthrow = "";
73+
private boolean waitForPortPrint = true;
74+
private String expectedOutputBeforeThrow = "";
7275

7376
private Launcher(String mainClass) {
7477
this.mainClass = mainClass;
@@ -101,32 +104,54 @@ public Launcher setSuspended(boolean value) {
101104
return this;
102105
}
103106

107+
// required to pass non null port with address and emit string before the throw
108+
public Launcher enableOnThrow(String value, String expectedOutputBeforeThrow) {
109+
this.onthrow = value;
110+
this.waitForPortPrint = false;
111+
this.expectedOutputBeforeThrow = expectedOutputBeforeThrow;
112+
return this;
113+
}
114+
104115
public ProcessBuilder prepare() {
105116
List<String> debuggeeArgs = new LinkedList<>();
106117
if (vmOptions != null) {
107118
debuggeeArgs.add(vmOptions);
108119
}
120+
String onthrowArgs = onthrow.isEmpty() ? "" : ",onthrow=" + onthrow + ",launch=exit";
109121
debuggeeArgs.add("-agentlib:jdwp=transport=" + transport
110122
+ (address == null ? "" : ",address=" + address)
111-
+ ",server=y,suspend=" + (suspended ? "y" : "n"));
123+
+ ",server=y,suspend=" + (suspended ? "y" : "n")
124+
+ onthrowArgs);
112125
debuggeeArgs.addAll(options);
113126
debuggeeArgs.add(mainClass);
114127
return ProcessTools.createTestJvm(debuggeeArgs);
115128
}
116129

117130
public Debuggee launch(String name) {
118-
return new Debuggee(prepare(), name);
131+
return new Debuggee(prepare(), name, waitForPortPrint, expectedOutputBeforeThrow);
119132
}
120133
public Debuggee launch() {
121134
return launch("debuggee");
122135
}
123136
}
124137

125138
// starts the process, waits for "Listening for transport" output and detects transport/address
126-
private Debuggee(ProcessBuilder pb, String name) {
139+
private Debuggee(ProcessBuilder pb, String name, boolean waitForPortPrint, String expectedOutputBeforeThrow) {
127140
// debuggeeListen[0] - transport, debuggeeListen[1] - address
128141
String[] debuggeeListen = new String[2];
129142
Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(.+)\\b");
143+
if (!waitForPortPrint) {
144+
try {
145+
p = ProcessTools.startProcess(name, pb, s -> {output.add(s);}, s -> {
146+
return s.equals(expectedOutputBeforeThrow);
147+
}, 30, TimeUnit.SECONDS);
148+
} catch (IOException | InterruptedException | TimeoutException ex) {
149+
throw new RuntimeException("failed to launch debuggee", ex);
150+
}
151+
transport = null;
152+
address = null;
153+
return;
154+
}
130155
try {
131156
p = ProcessTools.startProcess(name, pb,
132157
s -> output.add(s), // output consumer
@@ -175,10 +200,16 @@ public String getOutput() {
175200
}
176201

177202
String getTransport() {
203+
if (transport == null) {
204+
throw new IllegalStateException("transport is not available");
205+
}
178206
return transport;
179207
}
180208

181209
public String getAddress() {
210+
if (address == null) {
211+
throw new IllegalStateException("address is not available");
212+
}
182213
return address;
183214
}
184215

0 commit comments

Comments
 (0)