diff --git a/src/java.se/share/data/jdwp/jdwp.spec b/src/java.se/share/data/jdwp/jdwp.spec index 0c4c5ed5c24..323a48e591e 100644 --- a/src/java.se/share/data/jdwp/jdwp.spec +++ b/src/java.se/share/data/jdwp/jdwp.spec @@ -2104,10 +2104,10 @@ JDWP "Java(tm) Debug Wire Protocol" "command and resumption of thread execution, the " "state of the stack is undefined. " "

" - "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." "

" "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.") diff --git a/src/jdk.jdi/share/classes/com/sun/jdi/ThreadReference.java b/src/jdk.jdi/share/classes/com/sun/jdi/ThreadReference.java index 3cf0cb96960..9a47111677f 100644 --- a/src/jdk.jdi/share/classes/com/sun/jdi/ThreadReference.java +++ b/src/jdk.jdi/share/classes/com/sun/jdi/ThreadReference.java @@ -436,10 +436,10 @@ public interface ThreadReference extends ObjectReference { * this method and resumption of thread execution, the * state of the stack is undefined. *

- * 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. *

* 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 diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java index 79e11e32751..0f99fe99871 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java @@ -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( diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index c36cc6d5481..e5ca8b77649 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -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 diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002.java index f5b8a7581a9..c4bbfe51b82 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002.java @@ -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; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002a.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002a.java index 9664271bd73..1351c7dacfd 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002a.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ThreadReference/forceEarlyReturn/forceEarlyReturn002/forceEarlyReturn002a.java @@ -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); } diff --git a/test/jdk/ProblemList-Virtual.txt b/test/jdk/ProblemList-Virtual.txt index 6fb6b69e47d..b754527ecfe 100644 --- a/test/jdk/ProblemList-Virtual.txt +++ b/test/jdk/ProblemList-Virtual.txt @@ -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 diff --git a/test/jdk/com/sun/jdi/ForceEarlyReturnTest.java b/test/jdk/com/sun/jdi/ForceEarlyReturnTest.java new file mode 100644 index 00000000000..97b3db044d3 --- /dev/null +++ b/test/jdk/com/sun/jdi/ForceEarlyReturnTest.java @@ -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 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:"); + } +} diff --git a/test/jdk/com/sun/jdi/libForceEarlyReturnTestTarg.c b/test/jdk/com/sun/jdi/libForceEarlyReturnTestTarg.c new file mode 100644 index 00000000000..bc8b69ba40d --- /dev/null +++ b/test/jdk/com/sun/jdi/libForceEarlyReturnTestTarg.c @@ -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 +#include + +#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); +}