ee9776fa23
Reviewed-by: amenkov, cjplummer
362 lines
13 KiB
Java
362 lines
13 KiB
Java
/*
|
|
* 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 popFrames() on threads in various states not covered
|
|
* well by other tests. Most notably, this test includes
|
|
* test cases for a suspended but unmounted virtual thread.
|
|
* It is mostly for testing for OpaqueFrameException and
|
|
* NativeMethodException.
|
|
*
|
|
* @run build TestScaffold VMConnection TargetListener TargetAdapter
|
|
* @run compile -g PopFramesTest.java
|
|
* @run driver PopFramesTest SLEEP_NATIVE
|
|
* @run driver PopFramesTest LOOP_NATIVE
|
|
* @run driver PopFramesTest SLEEP_PRENATIVE
|
|
* @run driver PopFramesTest LOOP_PRENATIVE
|
|
* @run driver PopFramesTest SLEEP_NONATIVE
|
|
* @run driver PopFramesTest LOOP_NONATIVE
|
|
*/
|
|
import com.sun.jdi.*;
|
|
import com.sun.jdi.event.*;
|
|
import java.util.*;
|
|
|
|
/*
|
|
* There are six test modes covered by this test:
|
|
* SLEEP_NATIVE
|
|
* LOOP_NATIVE
|
|
* SLEEP_PRENATIVE
|
|
* LOOP_PRENATIVE
|
|
* SLEEP_NONATIVE
|
|
* LOOP_NONATIVE
|
|
*
|
|
* SLEEP: the debuggee blocks in Thread.sleep().
|
|
* LOOP: the debuggee sits in a tight loop.
|
|
* NATIVE: there is a native frame within the set of frames to pop.
|
|
* PRENATIVE: there is a native frame before the set of frames to pop.
|
|
* NONATIVE: there is no native frame (purposefully) present in the stack.
|
|
*
|
|
* In all cases the thread is suspended and errors such as IllegalArgumentException
|
|
* and InvalidStackFrameException should not happen. The popFrames() calls should
|
|
* either pass, or produce OpaqueFrameException or NativeMethodException.
|
|
*
|
|
* Call stacks for each test mode (and expected result):
|
|
* - Note in all cases the popMethod() frame is the frame passed to popFrames().
|
|
* - Note that Thread.sleep() usually results in the native Thread.sleep0() frame
|
|
* being at the top of the stack. However, for a mounted virtual thread that is
|
|
* not pinned, it does not result in any native frames due to how the VM
|
|
* parks non-pinned virtual threads.
|
|
*
|
|
* SLEEP_NATIVE (NativeMethodException):
|
|
* Thread.sleep() + methods called by Thread.sleep()
|
|
* loopOrSleep()
|
|
* upcallMethod()
|
|
* doUpcall() <-- native method
|
|
* popMethod()
|
|
* main()
|
|
*
|
|
* LOOP_NATIVE (NativeMethodException):
|
|
* loopOrSleep() <-- tight loop
|
|
* upcallMethod()
|
|
* doUpcall() <-- native method
|
|
* popMethod()
|
|
* main()
|
|
*
|
|
* SLEEP_PRENATIVE (NativeMethodException due to Thread.sleep() blocking in a native method):
|
|
* Thread.sleep() + methods called by Thread.sleep()
|
|
* loopOrSleep()
|
|
* popMethod()
|
|
* upcallMethod()
|
|
* doUpcall() <-- native method
|
|
* main()
|
|
*
|
|
* LOOP_PRENATIVE (no exception):
|
|
* loopOrSleep() <-- tight loop
|
|
* popMethod()
|
|
* upcallMethod()
|
|
* doUpcall() <-- native method
|
|
* main()
|
|
*
|
|
* SLEEP_NONATIVE (NativeMethodException for platform thread or OpaqueFrameException
|
|
* for virtual thread. See explanation in runTests().):
|
|
* Thread.sleep() + methods called by Thread.sleep()
|
|
* loopOrSleep()
|
|
* popMethod()
|
|
* main()
|
|
*
|
|
* LOOP_NONATIVE (no exception):
|
|
* loopOrSleep() <-- tight loop
|
|
* popMethod()
|
|
* main()
|
|
*/
|
|
|
|
class PopFramesTestTarg {
|
|
static TestMode mode;
|
|
|
|
static {
|
|
System.loadLibrary("PopFramesTestTarg");
|
|
}
|
|
|
|
/*
|
|
* This is the method whose frame (and all those after it) will be popped.
|
|
*/
|
|
public static void popMethod() {
|
|
System.out.println(" debuggee: in popMethod");
|
|
if (mode.isCallNative()) {
|
|
doUpcall();
|
|
} else {
|
|
loopOrSleep();
|
|
}
|
|
}
|
|
|
|
public static void loopOrSleep() {
|
|
if (mode.isDoLoop()) {
|
|
while (true);
|
|
} else {
|
|
try {
|
|
Thread.sleep(10000);
|
|
} catch (InterruptedException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static native void doUpcall(); // native method that will call upcallMethod()
|
|
|
|
public static void upcallMethod() {
|
|
if (mode.isCallPrenative()) {
|
|
popMethod();
|
|
} else {
|
|
loopOrSleep();
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println(" debuggee: Howdy!");
|
|
|
|
// We expect just one argument, which is the test mode, such as SLEEP_NONATIVE.
|
|
if (args.length != 1) {
|
|
throw new RuntimeException("Must pass 1 arguments to PopFramesTestTarg");
|
|
}
|
|
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);
|
|
|
|
if (mode.isCallNative()) {
|
|
popMethod(); // call popMethod() directly, and it will call out to native
|
|
} else if (mode.isCallPrenative()) {
|
|
doUpcall(); // call native method that will call back into java to call popMethod()
|
|
} else {
|
|
popMethod(); // call popMethod() directly
|
|
}
|
|
|
|
System.out.println(" debuggee: Goodbye from PopFramesTest!");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The different modes the test can be run in. See test description comment above.
|
|
*/
|
|
enum TestMode {
|
|
SLEEP_NATIVE,
|
|
LOOP_NATIVE,
|
|
SLEEP_PRENATIVE,
|
|
LOOP_PRENATIVE,
|
|
SLEEP_NONATIVE,
|
|
LOOP_NONATIVE;
|
|
|
|
// Returns true if debuggee should block in an infinite loop. Otherwise it calls Thread.sleep().
|
|
boolean isDoLoop() {
|
|
return this == LOOP_NATIVE || this == LOOP_PRENATIVE || this == LOOP_NONATIVE;
|
|
}
|
|
|
|
// Returns true if debuggee should introduce a native frame within the set of frames to pop.
|
|
boolean isCallNative() {
|
|
return this == LOOP_NATIVE || this == SLEEP_NATIVE;
|
|
}
|
|
|
|
// Returns true if debuggee should introduce a native frame before the set of frames to pop.
|
|
// The purpose is to cause the virtual thread to be pinned.
|
|
boolean isCallPrenative() {
|
|
return this == LOOP_PRENATIVE || this == SLEEP_PRENATIVE;
|
|
}
|
|
}
|
|
|
|
/********** test program **********/
|
|
|
|
public class PopFramesTest extends TestScaffold {
|
|
private static TestMode mode;
|
|
|
|
PopFramesTest(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_NONATIVE.
|
|
if (args.length != 1) {
|
|
throw new RuntimeException("Must pass one argument to PopFramesTestTarg");
|
|
}
|
|
mode = Enum.valueOf(TestMode.class, args[0]); // convert test mode string to an enum
|
|
|
|
/*
|
|
* The @run command looks something like:
|
|
* @run driver PopFramesTest SLEEP_NONATIVE
|
|
* We need to pass SLEEP_NONATIVE 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 PopFramesTest(newArgs).startTests();
|
|
}
|
|
|
|
StackFrame frameFor(ThreadReference thread, String methodName) throws Exception {
|
|
Iterator it = thread.frames().iterator();
|
|
|
|
while (it.hasNext()) {
|
|
StackFrame frame = (StackFrame)it.next();
|
|
if (frame.location().method().name().equals(methodName)) {
|
|
return frame;
|
|
}
|
|
}
|
|
failure("FAIL: " + methodName + " not on stack");
|
|
return null;
|
|
}
|
|
|
|
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("PopFramesTestTarg", "loopOrSleep", "()V");
|
|
ThreadReference mainThread = bpe.thread();
|
|
|
|
// 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 popFrames():");
|
|
|
|
/*
|
|
* Figure out which exception popFrames() should throw.
|
|
*/
|
|
Class expected_exception;
|
|
switch(mode) {
|
|
case SLEEP_NATIVE:
|
|
case LOOP_NATIVE:
|
|
case SLEEP_PRENATIVE:
|
|
/*
|
|
* For the two NATIVE cases, there is a native frame within the set of frames
|
|
* to pop. For the SLEEP_PRENATIVE case, there also ends up being a native
|
|
* frame. It will either be Thread.sleep0() for platform threads or
|
|
* Unsafe.park() for virtual threads. See the SLEEP_NATIVE comment below
|
|
* for more details.
|
|
*/
|
|
expected_exception = NativeMethodException.class;
|
|
break;
|
|
case LOOP_PRENATIVE:
|
|
case LOOP_NONATIVE:
|
|
/*
|
|
* For these two test cases, there are no native frames within the set of
|
|
* frames to pop, nor in the frame previous to the frame to pop, so no
|
|
* exception is expected.
|
|
*/
|
|
expected_exception = null;
|
|
break;
|
|
case SLEEP_NONATIVE:
|
|
/*
|
|
* 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 (DebuggeeWrapper.isVirtual()) {
|
|
expected_exception = OpaqueFrameException.class;
|
|
} else {
|
|
expected_exception = NativeMethodException.class;
|
|
}
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Bad test mode: " + mode);
|
|
}
|
|
|
|
/*
|
|
* Pop all the frames up to and including the popMethod() frame.
|
|
*/
|
|
try {
|
|
mainThread.popFrames(frameFor(mainThread, "popMethod"));
|
|
if (expected_exception != null) {
|
|
failure("failure: popFrames() did not get expected exception: " + expected_exception);
|
|
}
|
|
} catch (Exception ex) {
|
|
if (expected_exception == ex.getClass()) {
|
|
System.out.println("success: popFrames() got expected exception: " + ex);
|
|
} else {
|
|
failure("failure: popFrames() got unexpected exception: " + ex);
|
|
}
|
|
}
|
|
|
|
printStack(mainThread, "Debuggee stack after popFrames():");
|
|
|
|
/*
|
|
* 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("PopFramesTest failed");
|
|
}
|
|
System.out.println("Passed:");
|
|
}
|
|
}
|