8308819: add JDWP and JDI virtual thread support for ThreadReference.ForceEarlyReturn

Reviewed-by: sspitsyn, alanb
This commit is contained in:
Chris Plummer 2023-05-31 20:50:22 +00:00
parent e42a4b659a
commit 5531f6ba1b
9 changed files with 322 additions and 49 deletions

View File

@ -2104,10 +2104,10 @@ JDWP "Java(tm) Debug Wire Protocol"
"command and resumption of thread execution, the "
"state of the stack is undefined. "
"<p>"
"The target VM may not support, or may only provide limited support, for "
"this command when the thread is a virtual thread. It may, for example, "
"only support this command when the virtual thread is suspended at a "
"breakpoint or singlestep event."
"This command may be used to force a return from the current frame "
"of a virtual thread when it is suspended at an event. "
"An implementation may support forcing a return from the current frame "
"of a suspended virtual thread in other cases."
"<p>"
"No further instructions are executed in the called "
"method. Specifically, finally blocks are not executed. Note: "
@ -2147,7 +2147,7 @@ JDWP "Java(tm) Debug Wire Protocol"
(Error THREAD_NOT_SUSPENDED)
(Error OPAQUE_FRAME "Attempted to return early from a frame "
"corresponding to a native method, "
"the thread is a virtual thread and the target "
"the thread is a suspended virtual thread and the target "
"VM is unable to force its current frame to return, "
"or the implementation is unable to provide this "
"functionality on this frame.")

View File

@ -436,10 +436,10 @@ public interface ThreadReference extends ObjectReference {
* this method and resumption of thread execution, the
* state of the stack is undefined.
* <p>
* The target VM may not support, or may only provide limited support,
* for forcing a method to return when the thread is a virtual thread.
* It may, for example, only support this operation when the virtual
* thread is suspended at a breakpoint or singlestep event.
* This method may be used to force a return from the current frame
* of a virtual thread when it is suspended at an event.
* An implementation may support forcing a return from the current frame
* of a suspended virtual thread in other cases.
* <p>
* No further instructions are executed in the called
* method. Specifically, finally blocks are not executed. Note:
@ -484,7 +484,7 @@ public interface ThreadReference extends ObjectReference {
* @throws IncompatibleThreadStateException if this
* thread is not suspended.
*
* @throws OpaqueFrameException if this thread is a virtual thread and the
* @throws OpaqueFrameException if this thread is a suspended virtual thread and the
* target VM is unable to force the method to return.
*
* @throws NativeMethodException if the frame to be returned from

View File

@ -597,11 +597,10 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
} catch (JDWPException exc) {
switch (exc.errorCode()) {
case JDWP.Error.OPAQUE_FRAME:
if (meth.isNative()) {
throw new NativeMethodException();
} else {
assert isVirtual(); // can only happen with virtual threads
if (isVirtual() && !meth.isNative()) {
throw new OpaqueFrameException();
} else {
throw new NativeMethodException();
}
case JDWP.Error.THREAD_NOT_SUSPENDED:
throw new IncompatibleThreadStateException(

View File

@ -105,16 +105,6 @@ vmTestbase/nsk/jdb/repeat/repeat001/repeat001.java 8300707 generic-all
vmTestbase/nsk/jdi/ExceptionEvent/catchLocation/location002/TestDescription.java 8278470 generic-all
####
## JVMTI ForceEarlyReturn not supported for vthreads (JVMTI_ERROR_OPAQUE_FRAME)
## Note forceEarlyReturn002 was converted to support vthreads. The rest were not
## since there is no added value (JVMTI_ERROR_OPAQUE_FRAME is expected).
vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002.java
vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn014/forceEarlyReturn014.java 8285415 generic-all
vmTestbase/nsk/jdi/stress/serial/forceEarlyReturn001/TestDescription.java 8285415 generic-all
vmTestbase/nsk/jdi/stress/serial/forceEarlyReturn002/TestDescription.java 8285415 generic-all
###
# This test always times out on windows. This is due to the test forcing OOME in the
# debuggee, which has the side affect of making the Read-Poller thread exit. Because

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -147,30 +147,17 @@ public class forceEarlyReturn002 extends ForceEarlyReturnDebugger {
// get value for early return
ObjectReference returnValue = (ObjectReference) referenceType.getValue(referenceType.fieldByName("expectedValue"));
boolean vthreadMode = "Virtual".equals(System.getProperty("main.wrapper"));
try {
// don't expect any exception, except for vthreads expect OpaqueFrameException
// don't expect any exception
threadReference.forceEarlyReturn(returnValue);
if (vthreadMode) {
setSuccess(false);
log.complain("Expected OpaqueFrameException");
}
} catch (Exception e) {
if (vthreadMode && (e instanceof OpaqueFrameException)) {
// pass
} else {
setSuccess(false);
log.complain("Unexpected exception: " + e);
e.printStackTrace(log.getOutStream());
}
setSuccess(false);
log.complain("Unexpected exception: " + e);
e.printStackTrace(log.getOutStream());
}
if (vthreadMode) {
// MethodExit event won't be as expected if using vthreads, so just resume
threadReference.resume();
} else {
testMethodExitEvent(threadReference, ClassUsingTestClass.breakpointMethodName);
}
testMethodExitEvent(threadReference, ClassUsingTestClass.breakpointMethodName);
if (!isDebuggeeReady())
return;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -83,9 +83,7 @@ public class forceEarlyReturn002a extends AbstractJDIDebuggee {
classUsingTestClass.createExpectedValue();
Object value = classUsingTestClass.testClassMethod();
boolean vthreadMode = "Virtual".equals(System.getProperty("main.wrapper"));
// expectedValue should be set as expected unless in vthread mode
if (vthreadMode == (ClassUsingTestClass.expectedValue == value)) {
if (ClassUsingTestClass.expectedValue != value) {
setSuccess(false);
log.complain("Unexpected result of testClassMethod: " + value);
}

View File

@ -28,7 +28,6 @@ com/sun/jdi/EATests.java#id0 8264699 generic-
#
com/sun/jdi/DeferredStepTest.java 8285422 generic-all
com/sun/jdi/EarlyReturnTest.java 8285422 generic-all
com/sun/jdi/ExceptionEvents.java 8285422 generic-all
com/sun/jdi/JdbMethodExitTest.java 8285422 generic-all
com/sun/jdi/JdbStepTest.java 8285422 generic-all

View File

@ -0,0 +1,263 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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.
*/
/**
* @test
* @summary Call forceEarlyReturn() on threads in various states not covered
* well by other tests. Most notably, this test includes a
* test case for a suspended but unmounted virtual thread.
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile -g ForceEarlyReturnTest.java
* @run driver ForceEarlyReturnTest NATIVE
* @run driver ForceEarlyReturnTest LOOP
* @run driver ForceEarlyReturnTest SLEEP
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import java.util.*;
/*
* There are three test modes covered by this test:
* NATIVE: the debuggee sits in a native method.
* SLEEP: the debuggee blocks in Thread.sleep().
* LOOP: the debuggee sits in a tight loop.
*
* In all cases the thread is suspended and errors such as IllegalArgumentException
* and InvalidStackFrameException should not happen. The forceEarlyReturn() calls should
* either pass, or produce OpaqueFrameException or NativeMethodException.
*
* Call stacks for each test mode (and expected result):
*
* NATIVE (NativeMethodException):
* nativeMethod() <-- native method, which sleeps
* loopOrSleep()
* main()
*
* LOOP (no exception):
* loopOrSleep() <-- tight loop
* main()
*
* SLEEP (NativeMethodException for platform thread or OpaqueFrameException
* for virtual thread. See explanation in runTests().):
* Thread.sleep() + methods called by Thread.sleep()
* loopOrSleep()
* main()
*/
class ForceEarlyReturnTestTarg {
static TestMode mode;
static {
System.loadLibrary("ForceEarlyReturnTestTarg");
}
public static void loopOrSleep() {
switch (mode) {
case TestMode.LOOP:
while (true);
case TestMode.SLEEP:
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
break;
case TestMode.NATIVE:
nativeMethod();
break;
}
}
public static native void nativeMethod(); // native method that does a very long sleep
public static void main(String[] args) {
System.out.println(" debuggee: Howdy!");
// We expect just one argument, which is the test mode, such as SLEEP.
if (args.length != 1) {
throw new RuntimeException("Must pass 1 arguments to ForceEarlyReturnTestTarg");
}
System.out.println(" debuggee: args[0]: " + args[0]);
mode = Enum.valueOf(TestMode.class, args[0]); // convert test mode string to an enum
System.out.println(" debuggee: test mode: " + mode);
loopOrSleep();
System.out.println(" debuggee: Goodbye from ForceEarlyReturnTest!");
}
}
/*
* The different modes the test can be run in. See test description comment above.
*/
enum TestMode {
NATIVE,
SLEEP,
LOOP;
}
/********** test program **********/
public class ForceEarlyReturnTest extends TestScaffold {
private static TestMode mode;
ForceEarlyReturnTest(String args[]) {
super(args);
}
public static void main(String[] args) throws Exception {
// We should get one argument that indicates the test mode, such as SLEEP.
if (args.length != 1) {
throw new RuntimeException("Must pass one argument to ForceEarlyReturnTestTarg");
}
mode = Enum.valueOf(TestMode.class, args[0]); // convert test mode string to an enum
/*
* The @run command looks something like:
* @run driver ForceEarlyReturnTest SLEEP
* We need to pass SLEEP to the debuggee. We also need to insert
* -Djava.library.path so the native method can be accessed if called.
*/
String nativePath = "-Djava.library.path=" + System.getProperty("java.library.path");
String[] newArgs = new String[2];
newArgs[0] = nativePath;
newArgs[1] = args[0]; // pass test mode, such as SLEEP_NONATIVE
new ForceEarlyReturnTest(newArgs).startTests();
}
public void printStack(ThreadReference thread, String msg) throws Exception {
System.out.println(msg);
List<StackFrame> stack_frames = thread.frames();
int i = 0;
String sourceName;
for (StackFrame f : stack_frames) {
try {
sourceName = f.location().sourceName();
} catch (AbsentInformationException aie) {
sourceName = "Unknown source";
}
System.out.println("frame[" + i++ +"]: " + f.location().method() +
" (bci:"+ f.location().codeIndex() + ")" +
" (" + sourceName + ":"+ f.location().lineNumber() + ")");
}
}
/********** test core **********/
protected void runTests() throws Exception {
BreakpointEvent bpe = startTo("ForceEarlyReturnTestTarg", "loopOrSleep", "()V");
ThreadReference mainThread = bpe.thread();
boolean is_vthread_mode = "Virtual".equals(System.getProperty("main.wrapper"));
// Resume main thread until it is in Thread.sleep() or the infinite loop.
mainThread.resume();
try {
Thread.sleep(1000); // give thread chance to get into Thread.sleep() or loop
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
mainThread.suspend(); // Suspend thread while in Thread.sleep() or loop
printStack(mainThread, "Debuggee stack before forceEarlyReturn():");
/*
* Figure out which exception forceEarlyReturn() should throw.
*/
Class expected_exception;
switch(mode) {
case NATIVE:
/*
* There is a native frame on the top of the stack, so we expect NativeMethodException.
*/
expected_exception = NativeMethodException.class;
break;
case LOOP:
/*
* There is a java frame on the top of the stack, so we expect no exception.
*/
expected_exception = null;
break;
case SLEEP:
/*
* For platform threads, Thread.sleep() results in the Thread.sleep0() native
* frame on the stack, so the end result is NativeMethodException. For virtual
* threads it is not quite so simple. If the thead is pinned (such as when
* there is already a native method on the stack), you end up in
* VirtualThread.parkOnCarrierThread(), which calls Unsafe.park(), which is a
* native method, so again this results in NativeMethodException. However, for
* a virtual thread that is not pinned (which is true for this test case), you
* end up with no native methods on the stack due to how Continuation.yield()
* works. So you have an unmounted virtual thread with no native frames, which
* results in OpaqueFrameException being thrown.
*/
if (is_vthread_mode) {
expected_exception = OpaqueFrameException.class;
} else {
expected_exception = NativeMethodException.class;
}
break;
default:
throw new RuntimeException("Bad test mode: " + mode);
}
/*
* Call ThreadReference.forceEarlyReturn() and check for errors.
*/
try {
if (is_vthread_mode && mode == TestMode.SLEEP) {
// For this test case with virtual threads, the topmost frame is for
// Continuation.yield0(), which returns a boolean.
BooleanValue theValue = vm().mirrorOf(true);
mainThread.forceEarlyReturn(theValue);
} else {
// For all other cases, the topmost frame will be one that returns void.
VoidValue theValue = vm().mirrorOfVoid();
mainThread.forceEarlyReturn(theValue);
}
if (expected_exception != null) {
failure("failure: forceEarlyReturn() did not get expected exception: " + expected_exception);
} else {
System.out.println("success: no exception for forceEarlyReturn()");
}
} catch (Exception ex) {
if (expected_exception == ex.getClass()) {
System.out.println("success: forceEarlyReturn() got expected exception: " + ex);
} else {
failure("failure: forceEarlyReturn() got unexpected exception: " + ex);
}
}
/*
* Most tests do a listenUntilVMDisconnect() here, but there is no real need for it
* with this test, and doing so would require finding a way to get the debuggee
* to exit the endless loop it might be in. When we return, TestScaffold will
* call TestScaffold.shutdown(), causing the debuggee process to be terminated quickly.
*/
if (testFailed) {
throw new Exception("ForceEarlyReturnTest failed");
}
System.out.println("Passed:");
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include "jni.h"
/*
* Class: NativeMethod
* Method: nativeMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_ForceEarlyReturnTestTarg_nativeMethod(JNIEnv *env, jobject obj) {
/* Just spin. We don't need to ever return from here. */
while(JNI_TRUE);
}