Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK-8317920: JDWP-agent sends broken exception event with onthrow option #16145

Closed
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 28 additions & 12 deletions src/jdk.jdwp.agent/share/native/libjdwp/debugInit.c
Expand Up @@ -102,7 +102,7 @@ static void JNICALL cbEarlyVMDeath(jvmtiEnv*, JNIEnv *);
static void JNICALL cbEarlyException(jvmtiEnv*, JNIEnv *,
jthread, jmethodID, jlocation, jobject, jmethodID, jlocation);

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

/*
Expand Down Expand Up @@ -391,7 +391,7 @@ cbEarlyVMInit(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thread)
EXIT_ERROR(AGENT_ERROR_INTERNAL,"VM dead at VM_INIT time");
}
if (initOnStartup)
initialize(env, thread, EI_VM_INIT);
initialize(env, thread, EI_VM_INIT, NULL);
vmInitialized = JNI_TRUE;
LOG_MISC(("END cbEarlyVMInit"));
}
Expand Down Expand Up @@ -444,6 +444,19 @@ cbEarlyException(jvmtiEnv *jvmti_env, JNIEnv *env,
LOG_MISC(("VM is not initialized yet"));
return;
}
EventInfo info;
info.ei = EI_EXCEPTION;
info.thread = thread;
info.clazz = getMethodClass(jvmti_env, method);
info.method = method;
info.location = location;
info.object = exception;
if (gdata->vthreadsSupported) {
info.is_vthread = isVThread(thread);
}
info.u.exception.catch_clazz = getMethodClass(jvmti_env, catch_method);
info.u.exception.catch_method = catch_method;
info.u.exception.catch_location = catch_location;

/*
* We want to preserve any current exception that might get wiped
Expand All @@ -458,24 +471,22 @@ cbEarlyException(jvmtiEnv *jvmti_env, JNIEnv *env,
if (initOnUncaught && catch_method == NULL) {

LOG_MISC(("Initializing on uncaught exception"));
initialize(env, thread, EI_EXCEPTION);
initialize(env, thread, EI_EXCEPTION, &info);

} else if (initOnException != NULL) {

jclass clazz;

/* Get class of exception thrown */
clazz = JNI_FUNC_PTR(env,GetObjectClass)(env, exception);
if ( clazz != NULL ) {
jclass exception_clazz = JNI_FUNC_PTR(env, GetObjectClass)(env, exception);
/* check class of exception thrown */
if ( exception_clazz != NULL ) {
char *signature = NULL;
/* initing on throw, check */
error = classSignature(clazz, &signature, NULL);
error = classSignature(exception_clazz, &signature, NULL);
LOG_MISC(("Checking specific exception: looking for %s, got %s",
initOnException, signature));
if ( (error==JVMTI_ERROR_NONE) &&
(strcmp(signature, initOnException) == 0)) {
LOG_MISC(("Initializing on specific exception"));
initialize(env, thread, EI_EXCEPTION);
initialize(env, thread, EI_EXCEPTION, &info);
} else {
error = AGENT_ERROR_INTERNAL; /* Just to cause restore */
}
Expand Down Expand Up @@ -616,9 +627,11 @@ jniFatalError(JNIEnv *env, const char *msg, jvmtiError error, int exit_code)

/*
* Initialize debugger back end modules
*
* @param opt_info optional event info to use, might be null
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
*/
static void
initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei)
initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei, EventInfo *opt_info)
{
jvmtiError error;
EnumerateArg arg;
Expand Down Expand Up @@ -712,6 +725,9 @@ initialize(JNIEnv *env, jthread thread, EventIndex triggering_ei)
initEventBag = eventHelper_createEventBag();
(void)memset(&info,0,sizeof(info));
info.ei = triggering_ei;
if (opt_info != NULL) {
info = *opt_info;
}
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
eventHelper_recordEvent(&info, 0, suspendPolicy, initEventBag);
(void)eventHelper_reportEvents(currentSessionID, initEventBag);
bagDestroyBag(initEventBag);
Expand Down Expand Up @@ -1368,7 +1384,7 @@ JNIEXPORT char const* JNICALL debugInit_startDebuggingViaCommand(JNIEnv* env, jt
if (!startedViaJcmd) {
startedViaJcmd = JNI_TRUE;
is_first_start = JNI_TRUE;
initialize(env, thread, EI_VM_INIT);
initialize(env, thread, EI_VM_INIT, NULL);
}

bagEnumerateOver(transports, getFirstTransport, &spec);
Expand Down
161 changes: 161 additions & 0 deletions test/jdk/com/sun/jdi/JdwpOnThrowTest.java
@@ -0,0 +1,161 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import com.sun.jdi.Bootstrap;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.ListeningConnector;
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
import com.sun.jdi.event.EventIterator;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.request.EventRequestManager;
import jdk.test.lib.Utils;
import lib.jdb.Debuggee;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/*
* @test
* @bug 8317920
* @summary Tests for JDWP agent to send valid exception event with onthrow option
* @library /test/lib
*
* @build ThrowCaughtException JdwpOnThrowTest
* @run main/othervm JdwpOnThrowTest
*/
public class JdwpOnThrowTest {

private static long TIMEOUT = 10000;

public static void main(String[] args) throws Exception {
int port = findFreePort();
try (Debuggee debuggee = Debuggee.launcher("ThrowCaughtException").setAddress("localhost:" + port)
.enableOnThrow("Ex", "Start").setSuspended(true).launch()) {
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
VirtualMachine vm = null;
try {
vm = attach("localhost", "" + port);
EventQueue queue = vm.eventQueue();
log("Waiting for exception event");
long start = System.currentTimeMillis();
while (start + TIMEOUT > System.currentTimeMillis()) {
EventSet eventSet = queue.remove(TIMEOUT);
EventIterator eventIterator = eventSet.eventIterator();
while(eventIterator.hasNext() && start + TIMEOUT > System.currentTimeMillis()) {
Event event = eventIterator.next();
if (event instanceof ExceptionEvent ex) {
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
if (ex.exception() == null) {
throw new RuntimeException("Exception is null");
}
if (ex.exception().type() == null) {
throw new RuntimeException("Exception type is null");
}
if (ex.exception().referenceType() == null) {
throw new RuntimeException("Exception reference type is null");
}
if (ex.catchLocation() == null) {
throw new RuntimeException("Exception catch location is null");
}
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
if (!ex.location().equals(ex.thread().frame(0).location())) {
throw new RuntimeException(
String.format("Throw location %s and location if first frame %s are not equal",
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
ex.location(), ex.thread().frame(0).location()));
}
if (!ex.exception().type().name().equals("Ex")) {
throw new RuntimeException("Exception has wrong type: " + ex.exception().type().name());
}
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
log("Received exception event: " + event);
vm.dispose();
return;
}
log("Received event: " + event);
}
}
throw new RuntimeException("ERROR: failed to receive exception event");
} catch (IOException ex) {
throw new RuntimeException("ERROR: failed to attach", ex);
}
}
}

private static int findFreePort() {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private static String ATTACH_CONNECTOR = "com.sun.jdi.SocketAttach";
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
// cache socket attaching connector
private static AttachingConnector attachingConnector;
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved

private static VirtualMachine attach(String address, String port) throws IOException {
if (attachingConnector == null) {
attachingConnector = (AttachingConnector)getConnector(ATTACH_CONNECTOR);
}
Map<String, Connector.Argument> args = attachingConnector.defaultArguments();
setConnectorArg(args, "hostname", address);
setConnectorArg(args, "port", port);
try {
return attachingConnector.attach(args);
} catch (IllegalConnectorArgumentsException e) {
// unexpected.. wrap in RuntimeException
throw new RuntimeException(e);
}
}

private static Connector getConnector(String name) {
List<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
for (Iterator<Connector> iter = connectors.iterator(); iter.hasNext(); ) {
Connector connector = iter.next();
if (connector.name().equalsIgnoreCase(name)) {
return connector;
}
}
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException("Connector " + name + " not found");
}

private static void setConnectorArg(Map<String, Connector.Argument> args, String name, String value) {
Connector.Argument arg = args.get(name);
if (arg == null) {
throw new IllegalArgumentException("Argument " + name + " is not defined");
}
arg.setValue(value);
}

private static void log(Object o) {
System.out.println(String.valueOf(o));
}

}
36 changes: 36 additions & 0 deletions test/jdk/com/sun/jdi/ThrowCaughtException.java
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

public class ThrowCaughtException {
public static void main(String args[]) throws Exception {
try {
System.out.println("Start");
throw new Ex();
} catch (Exception e) {
System.out.println(e);
}
}
}

class Ex extends RuntimeException {
}
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 34 additions & 3 deletions test/jdk/com/sun/jdi/lib/jdb/Debuggee.java
Expand Up @@ -68,6 +68,9 @@ public static class Launcher {
private String transport = "dt_socket";
private String address = null;
private boolean suspended = true;
private String onthrow = "";
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
private boolean waitForPortPrint = true;
private String expectedOutputBeforeThrow = "";

private Launcher(String mainClass) {
this.mainClass = mainClass;
Expand Down Expand Up @@ -100,30 +103,52 @@ public Launcher setSuspended(boolean value) {
return this;
}

// required to pass non null port with address and emit string before the throw
public Launcher enableOnThrow(String value, String expectedOutputBeforeThrow) {
this.onthrow = value;
this.waitForPortPrint = false;
this.expectedOutputBeforeThrow = expectedOutputBeforeThrow;
return this;
}

public ProcessBuilder prepare() {
List<String> debuggeeArgs = new LinkedList<>();
if (vmOptions != null) {
debuggeeArgs.add(vmOptions);
}
String onthrowArgs = onthrow.isEmpty() ? "" : ",onthrow=" + onthrow + ",launch=exit";
debuggeeArgs.add("-agentlib:jdwp=transport=" + transport
+ (address == null ? "" : ",address=" + address)
+ ",server=y,suspend=" + (suspended ? "y" : "n"));
+ ",server=y,suspend=" + (suspended ? "y" : "n")
+ onthrowArgs);
debuggeeArgs.addAll(options);
debuggeeArgs.add(mainClass);
return ProcessTools.createTestJvm(debuggeeArgs);
}

public Debuggee launch(String name) {
return new Debuggee(prepare(), name);
return new Debuggee(prepare(), name, waitForPortPrint, expectedOutputBeforeThrow);
}
public Debuggee launch() {
return launch("debuggee");
}
}

// starts the process, waits for "Listening for transport" output and detects transport/address
private Debuggee(ProcessBuilder pb, String name) {
private Debuggee(ProcessBuilder pb, String name, boolean waitForPortPrint, String expectedOutputBeforeThrow) {
JDWP.ListenAddress[] listenAddress = new JDWP.ListenAddress[1];
if (!waitForPortPrint) {
try {
p = ProcessTools.startProcess(name, pb, s -> {output.add(s);}, s -> {
return s.equals(expectedOutputBeforeThrow);
}, 30, TimeUnit.SECONDS);
} catch (IOException | InterruptedException | TimeoutException ex) {
throw new RuntimeException("failed to launch debuggee", ex);
}
transport = null;
address = null;
return;
}
try {
p = ProcessTools.startProcess(name, pb,
s -> output.add(s), // output consumer
Expand Down Expand Up @@ -167,10 +192,16 @@ public String getOutput() {
}

String getTransport() {
if (address == null) {
parttimenerd marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalStateException("address is not available");
}
return transport;
}

public String getAddress() {
if (address == null) {
throw new IllegalStateException("address is not available");
}
return address;
}

Expand Down