diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index dd7cecdd9c2..93dd0af65e7 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -926,6 +926,7 @@ public: _method_ForceInline, _method_DontInline, _method_ChangesCurrentThread, + _method_JvmtiHideEvents, _method_JvmtiMountTransition, _method_InjectedProfile, _method_LambdaForm_Compiled, @@ -1830,6 +1831,11 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data, if (!privileged) break; // only allow in privileged code return _method_ChangesCurrentThread; } + case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_JvmtiHideEvents_signature): { + if (_location != _in_method) break; // only allow for methods + if (!privileged) break; // only allow in privileged code + return _method_JvmtiHideEvents; + } case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_JvmtiMountTransition_signature): { if (_location != _in_method) break; // only allow for methods if (!privileged) break; // only allow in privileged code @@ -1917,6 +1923,8 @@ void MethodAnnotationCollector::apply_to(const methodHandle& m) { m->set_dont_inline(); if (has_annotation(_method_ChangesCurrentThread)) m->set_changes_current_thread(); + if (has_annotation(_method_JvmtiHideEvents)) + m->set_jvmti_hide_events(); if (has_annotation(_method_JvmtiMountTransition)) m->set_jvmti_mount_transition(); if (has_annotation(_method_InjectedProfile)) diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 58e1551e20c..014a8a00c7b 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -306,6 +306,7 @@ class SerializeClosure; template(jdk_internal_vm_annotation_Stable_signature, "Ljdk/internal/vm/annotation/Stable;") \ \ template(jdk_internal_vm_annotation_ChangesCurrentThread_signature, "Ljdk/internal/vm/annotation/ChangesCurrentThread;") \ + template(jdk_internal_vm_annotation_JvmtiHideEvents_signature, "Ljdk/internal/vm/annotation/JvmtiHideEvents;") \ template(jdk_internal_vm_annotation_JvmtiMountTransition_signature, "Ljdk/internal/vm/annotation/JvmtiMountTransition;") \ \ /* Support for JSR 292 & invokedynamic (JDK 1.7 and above) */ \ diff --git a/src/hotspot/share/oops/constMethodFlags.hpp b/src/hotspot/share/oops/constMethodFlags.hpp index 236f25ea746..031ebe44a96 100644 --- a/src/hotspot/share/oops/constMethodFlags.hpp +++ b/src/hotspot/share/oops/constMethodFlags.hpp @@ -60,6 +60,7 @@ class ConstMethodFlags { flag(jvmti_mount_transition , 1 << 18) \ flag(deprecated , 1 << 19) \ flag(deprecated_for_removal , 1 << 20) \ + flag(jvmti_hide_events , 1 << 21) \ /* end of list */ #define CM_FLAGS_ENUM_NAME(name, value) _misc_##name = value, diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 6ffaebcdfda..d42089d3a5c 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -746,6 +746,9 @@ public: bool changes_current_thread() const { return constMethod()->changes_current_thread(); } void set_changes_current_thread() { constMethod()->set_changes_current_thread(); } + bool jvmti_hide_events() const { return constMethod()->jvmti_hide_events(); } + void set_jvmti_hide_events() { constMethod()->set_jvmti_hide_events(); } + bool jvmti_mount_transition() const { return constMethod()->jvmti_mount_transition(); } void set_jvmti_mount_transition() { constMethod()->set_jvmti_mount_transition(); } diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp index 20f48eee673..c28afbb1c51 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -584,7 +584,6 @@ JvmtiEnvBase::jvf_for_thread_and_depth(JavaThread* java_thread, jint depth) { javaVFrame *jvf = java_thread->last_java_vframe(®_map); jvf = JvmtiEnvBase::check_and_skip_hidden_frames(java_thread, jvf); - for (int d = 0; jvf != nullptr && d < depth; d++) { jvf = jvf->java_sender(); } @@ -652,22 +651,42 @@ JavaThread* JvmtiEnvBase::get_JavaThread_or_null(oop vthread) { return Continuation::is_continuation_mounted(java_thread, cont) ? java_thread : nullptr; } +// An unmounted vthread may have an empty stack. +// Otherwise, it always has the yield0() and yield() frames we need to hide. +// The methods yield0() and yield() are annotated with the @JvmtiHideEvents. +javaVFrame* +JvmtiEnvBase::skip_yield_frames_for_unmounted_vthread(javaVFrame* jvf) { + if (jvf == nullptr) { + return jvf; // empty stack is possible + } + assert(jvf->method()->jvmti_hide_events(), "sanity check"); + assert(jvf->method()->method_holder() == vmClasses::Continuation_klass(), "expected Continuation class"); + jvf = jvf->java_sender(); // skip yield0 frame + + assert(jvf != nullptr && jvf->method()->jvmti_hide_events(), "sanity check"); + assert(jvf->method()->method_holder() == vmClasses::Continuation_klass(), "expected Continuation class"); + jvf = jvf->java_sender(); // skip yield frame + return jvf; +} + +// A thread may have an empty stack. +// Otherwise, some top frames may heed to be hidden. +// Two cases are processed below: +// - top frame is annotated with @JvmtiMountTransition: just skip top frames with annotated methods +// - JavaThread is in VTMS transition: skip top frames until a frame annotated with @ChangesCurrentThread is found javaVFrame* JvmtiEnvBase::check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFrame* jvf) { - // The second condition is needed to hide notification methods. - if (!is_in_VTMS_transition && (jvf == nullptr || !jvf->method()->jvmti_mount_transition())) { - return jvf; // No frames to skip. + if (jvf == nullptr) { + return jvf; // empty stack is possible } - // Find jvf with a method annotated with @JvmtiMountTransition. - for ( ; jvf != nullptr; jvf = jvf->java_sender()) { - if (jvf->method()->jvmti_mount_transition()) { // Cannot actually appear in an unmounted continuation; they're never frozen. - jvf = jvf->java_sender(); // Skip annotated method. - break; + if (jvf->method()->jvmti_mount_transition()) { + // Skip frames annotated with @JvmtiMountTransition. + for ( ; jvf != nullptr && jvf->method()->jvmti_mount_transition(); jvf = jvf->java_sender()) { } - if (jvf->method()->changes_current_thread()) { - break; + } else if (is_in_VTMS_transition) { + // Skip frames above the frame annotated with @ChangesCurrentThread. + for ( ; jvf != nullptr && !jvf->method()->changes_current_thread(); jvf = jvf->java_sender()) { } - // Skip frame above annotated method. } return jvf; } @@ -678,17 +697,6 @@ JvmtiEnvBase::check_and_skip_hidden_frames(JavaThread* jt, javaVFrame* jvf) { return jvf; } -javaVFrame* -JvmtiEnvBase::check_and_skip_hidden_frames(oop vthread, javaVFrame* jvf) { - JvmtiThreadState* state = java_lang_Thread::jvmti_thread_state(vthread); - if (state == nullptr) { - // nothing to skip - return jvf; - } - jvf = check_and_skip_hidden_frames(java_lang_Thread::is_in_VTMS_transition(vthread), jvf); - return jvf; -} - javaVFrame* JvmtiEnvBase::get_vthread_jvf(oop vthread) { assert(java_lang_VirtualThread::state(vthread) != java_lang_VirtualThread::NEW, "sanity check"); @@ -707,12 +715,13 @@ JvmtiEnvBase::get_vthread_jvf(oop vthread) { return nullptr; } vframeStream vfs(java_thread); + assert(!java_thread->is_in_VTMS_transition(), "invariant"); jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame(); - jvf = check_and_skip_hidden_frames(java_thread, jvf); + jvf = check_and_skip_hidden_frames(false, jvf); } else { vframeStream vfs(cont); jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame(); - jvf = check_and_skip_hidden_frames(vthread, jvf); + jvf = skip_yield_frames_for_unmounted_vthread(jvf); } return jvf; } @@ -725,11 +734,9 @@ JvmtiEnvBase::get_cthread_last_java_vframe(JavaThread* jt, RegisterMap* reg_map_ bool cthread_with_cont = JvmtiEnvBase::is_cthread_with_continuation(jt); javaVFrame *jvf = cthread_with_cont ? jt->carrier_last_java_vframe(reg_map_p) : jt->last_java_vframe(reg_map_p); - // Skip hidden frames only for carrier threads - // which are in non-temporary VTMS transition. - if (jt->is_in_VTMS_transition()) { - jvf = check_and_skip_hidden_frames(jt, jvf); - } + + // Skip hidden frames for carrier threads only. + jvf = check_and_skip_hidden_frames(jt, jvf); return jvf; } @@ -1332,7 +1339,9 @@ JvmtiEnvBase::set_frame_pop(JvmtiThreadState* state, javaVFrame* jvf, jint depth if (jvf == nullptr) { return JVMTI_ERROR_NO_MORE_FRAMES; } - if (jvf->method()->is_native() || (depth == 0 && state->top_frame_is_exiting())) { + if (jvf->method()->is_native() || + (depth == 0 && state->top_frame_is_exiting()) || + (state->is_virtual() && jvf->method()->jvmti_hide_events())) { return JVMTI_ERROR_OPAQUE_FRAME; } assert(jvf->frame_pointer() != nullptr, "frame pointer mustn't be null"); @@ -1989,7 +1998,6 @@ void JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, jthread target) { JavaThread* current = JavaThread::current(); HandleMark hm(current); - JvmtiVTMSTransitionDisabler disabler(target); ThreadsListHandle tlh(current); JavaThread* java_thread = nullptr; diff --git a/src/hotspot/share/prims/jvmtiEnvBase.hpp b/src/hotspot/share/prims/jvmtiEnvBase.hpp index c6891fdeb1f..e8769d423c5 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.hpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.hpp @@ -366,9 +366,9 @@ class JvmtiEnvBase : public CHeapObj { static bool get_field_descriptor(Klass* k, jfieldID field, fieldDescriptor* fd); // check and skip frames hidden in mount/unmount transitions + static javaVFrame* skip_yield_frames_for_unmounted_vthread(javaVFrame* jvf); static javaVFrame* check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFrame* jvf); static javaVFrame* check_and_skip_hidden_frames(JavaThread* jt, javaVFrame* jvf); - static javaVFrame* check_and_skip_hidden_frames(oop vthread, javaVFrame* jvf); // check if virtual thread is not terminated (alive) static bool is_vthread_alive(oop vt); diff --git a/src/hotspot/share/prims/jvmtiImpl.cpp b/src/hotspot/share/prims/jvmtiImpl.cpp index 7b9821fe28a..ac7143e02b8 100644 --- a/src/hotspot/share/prims/jvmtiImpl.cpp +++ b/src/hotspot/share/prims/jvmtiImpl.cpp @@ -833,11 +833,6 @@ VM_VirtualThreadGetOrSetLocal::VM_VirtualThreadGetOrSetLocal(JvmtiEnv* env, Hand } javaVFrame *VM_VirtualThreadGetOrSetLocal::get_java_vframe() { - Thread* cur_thread = Thread::current(); - oop cont = java_lang_VirtualThread::continuation(_vthread_h()); - assert(cont != nullptr, "vthread contintuation must not be null"); - - javaVFrame* jvf = nullptr; JavaThread* java_thread = JvmtiEnvBase::get_JavaThread_or_null(_vthread_h()); bool is_cont_mounted = (java_thread != nullptr); @@ -845,22 +840,8 @@ javaVFrame *VM_VirtualThreadGetOrSetLocal::get_java_vframe() { _result = JVMTI_ERROR_THREAD_NOT_SUSPENDED; return nullptr; } + javaVFrame* jvf = JvmtiEnvBase::get_vthread_jvf(_vthread_h()); - if (is_cont_mounted) { - vframeStream vfs(java_thread); - - if (!vfs.at_end()) { - jvf = vfs.asJavaVFrame(); - jvf = JvmtiEnvBase::check_and_skip_hidden_frames(java_thread, jvf); - } - } else { - vframeStream vfs(cont); - - if (!vfs.at_end()) { - jvf = vfs.asJavaVFrame(); - jvf = JvmtiEnvBase::check_and_skip_hidden_frames(_vthread_h(), jvf); - } - } int d = 0; while ((jvf != nullptr) && (d < _depth)) { jvf = jvf->java_sender(); diff --git a/src/hotspot/share/prims/jvmtiThreadState.cpp b/src/hotspot/share/prims/jvmtiThreadState.cpp index b8d5cb4cdb5..743f112b9b6 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.cpp +++ b/src/hotspot/share/prims/jvmtiThreadState.cpp @@ -253,7 +253,7 @@ JvmtiVTMSTransitionDisabler::print_info() { #endif // disable VTMS transitions for one virtual thread -// no-op if thread is non-null and not a virtual thread +// disable VTMS transitions for all threads if thread is nullptr or a platform thread JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread) : _is_SR(false), _thread(thread) { @@ -266,6 +266,17 @@ JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread) if (!sync_protocol_enabled_permanently()) { JvmtiVTMSTransitionDisabler::inc_sync_protocol_enabled_count(); } + oop thread_oop = JNIHandles::resolve_external_guard(thread); + + // Target can be virtual or platform thread. + // If target is a platform thread then we have to disable VTMS transitions for all threads. + // It is by several reasons: + // - carrier threads can mount virtual threads which may cause incorrect behavior + // - there is no mechanism to disable transitions for a specific carrier thread yet + if (!java_lang_VirtualThread::is_instance(thread_oop)) { + _thread = nullptr; // target is a platform thread, switch to disabling VTMS transitions for all threads + } + if (_thread != nullptr) { VTMS_transition_disable_for_one(); // disable VTMS transitions for one virtual thread } else { @@ -316,9 +327,8 @@ JvmtiVTMSTransitionDisabler::VTMS_transition_disable_for_one() { JavaThread* thread = JavaThread::current(); HandleMark hm(thread); Handle vth = Handle(thread, JNIHandles::resolve_external_guard(_thread)); - if (!java_lang_VirtualThread::is_instance(vth())) { - return; // no-op if _thread is not a virtual thread - } + assert(java_lang_VirtualThread::is_instance(vth()), "sanity check"); + MonitorLocker ml(JvmtiVTMSTransition_lock); while (_SR_mode) { // suspender or resumer is a JvmtiVTMSTransitionDisabler monopolist @@ -468,7 +478,7 @@ JvmtiVTMSTransitionDisabler::start_VTMS_transition(jthread vthread, bool is_moun JvmtiVTSuspender::is_vthread_suspended(thread_id) ) { // Block while transitions are disabled or there are suspend requests. - if (ml.wait(10)) { + if (ml.wait(200)) { attempts--; } DEBUG_ONLY(if (attempts == 0) break;) @@ -525,7 +535,7 @@ JvmtiVTMSTransitionDisabler::finish_VTMS_transition(jthread vthread, bool is_mou (is_mount && JvmtiVTSuspender::is_vthread_suspended(thread_id)) ) { // Block while there are suspend requests. - if (ml.wait(10)) { + if (ml.wait(200)) { attempts--; } DEBUG_ONLY(if (attempts == 0) break;) diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 6eb20fa9e2e..8f389906f2a 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -56,6 +56,7 @@ import jdk.internal.vm.ThreadContainers; import jdk.internal.vm.annotation.ChangesCurrentThread; import jdk.internal.vm.annotation.Hidden; import jdk.internal.vm.annotation.IntrinsicCandidate; +import jdk.internal.vm.annotation.JvmtiHideEvents; import jdk.internal.vm.annotation.JvmtiMountTransition; import jdk.internal.vm.annotation.ReservedStackAccess; import sun.nio.ch.Interruptible; @@ -213,8 +214,14 @@ final class VirtualThread extends BaseVirtualThread { private static Runnable wrap(VirtualThread vthread, Runnable task) { return new Runnable() { @Hidden + @JvmtiHideEvents public void run() { - vthread.run(task); + vthread.notifyJvmtiStart(); // notify JVMTI + try { + vthread.run(task); + } finally { + vthread.notifyJvmtiEnd(); // notify JVMTI + } } }; } @@ -389,9 +396,6 @@ final class VirtualThread extends BaseVirtualThread { private void run(Runnable task) { assert Thread.currentThread() == this && state == RUNNING; - // notify JVMTI, may post VirtualThreadStart event - notifyJvmtiStart(); - // emit JFR event if enabled if (VirtualThreadStartEvent.isTurnedOn()) { var event = new VirtualThreadStartEvent(); @@ -405,20 +409,14 @@ final class VirtualThread extends BaseVirtualThread { } catch (Throwable exc) { dispatchUncaughtException(exc); } finally { - try { - // pop any remaining scopes from the stack, this may block - StackableScope.popAll(); + // pop any remaining scopes from the stack, this may block + StackableScope.popAll(); - // emit JFR event if enabled - if (VirtualThreadEndEvent.isTurnedOn()) { - var event = new VirtualThreadEndEvent(); - event.javaThreadId = threadId(); - event.commit(); - } - - } finally { - // notify JVMTI, may post VirtualThreadEnd event - notifyJvmtiEnd(); + // emit JFR event if enabled + if (VirtualThreadEndEvent.isTurnedOn()) { + var event = new VirtualThreadEndEvent(); + event.javaThreadId = threadId(); + event.commit(); } } } diff --git a/src/java.base/share/classes/jdk/internal/vm/Continuation.java b/src/java.base/share/classes/jdk/internal/vm/Continuation.java index 99d0c62aaec..b863def8e6a 100644 --- a/src/java.base/share/classes/jdk/internal/vm/Continuation.java +++ b/src/java.base/share/classes/jdk/internal/vm/Continuation.java @@ -36,6 +36,7 @@ import java.util.function.Supplier; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.vm.annotation.Hidden; +import jdk.internal.vm.annotation.JvmtiHideEvents; /** * A one-shot delimited continuation. @@ -305,6 +306,7 @@ public class Continuation { @Hidden @DontInline @IntrinsicCandidate + @JvmtiHideEvents private static void enter(Continuation c, boolean isContinue) { // This method runs in the "entry frame". // A yield jumps to this method's caller as if returning from this method. @@ -316,6 +318,7 @@ public class Continuation { } @Hidden + @JvmtiHideEvents private void enter0() { target.run(); } @@ -340,6 +343,7 @@ public class Continuation { * @throws IllegalStateException if not currently in the given {@code scope}, */ @Hidden + @JvmtiHideEvents public static boolean yield(ContinuationScope scope) { Continuation cont = JLA.getContinuation(currentCarrierThread()); Continuation c; @@ -352,6 +356,7 @@ public class Continuation { } @Hidden + @JvmtiHideEvents private boolean yield0(ContinuationScope scope, Continuation child) { preempted = false; diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiHideEvents.java b/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiHideEvents.java new file mode 100644 index 00000000000..06572e64546 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiHideEvents.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.vm.annotation; + +import java.lang.annotation.*; + +/** + * A method may be annotated with JvmtiHideEvents to hint that JVMTI events + * should not be generated in context of the annotated method. + * + * @implNote + * This annotation is only used for some VirtualThread and Continuation methods. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface JvmtiHideEvents { +} diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiMountTransition.java b/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiMountTransition.java index df0545e46b1..ff9f5d8bbbf 100644 --- a/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiMountTransition.java +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/JvmtiMountTransition.java @@ -28,8 +28,11 @@ package jdk.internal.vm.annotation; import java.lang.annotation.*; /** - * A method is annotated as "jvmti mount transition" if it starts - * or ends virtual thread mount state transition (VTMS transition). + * A method may be annotated with JvmtiMountTransition to hint + * it is desirable to omit it from JVMTI stack traces. + * Normally, a method is annotated with @JvmtiMountTransition if it starts + * or ends Virtual Thread Mount State (VTMS) transition, so the thread + * identity is undefined or different at method entry and exit. * * @implNote * This annotation is only used for VirtualThread methods. diff --git a/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java b/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java index 8a7177ec25d..221363f5621 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java +++ b/test/hotspot/jtreg/serviceability/jvmti/thread/GetFrameCount/framecnt01/framecnt01.java @@ -79,7 +79,7 @@ public class framecnt01 { } // this is too fragile, implementation can change at any time. - checkFrames(vThread1, false, 13); + checkFrames(vThread1, false, 11); LockSupport.unpark(vThread1); vThread1.join(); diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/CheckHiddenFrames/CheckHiddenFrames.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/CheckHiddenFrames/CheckHiddenFrames.java new file mode 100644 index 00000000000..1b6d409e30b --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/CheckHiddenFrames/CheckHiddenFrames.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 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 + * @bug 8341273 + * @summary Verifies JVMTI properly hides frames which are in VTMS transition + * @run main/othervm/native -agentlib:CheckHiddenFrames CheckHiddenFrames + */ + +public class CheckHiddenFrames { + static native boolean checkHidden(Thread t); + + static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + } + } + + public static void main(String[] args) throws Exception { + Thread thread = Thread.startVirtualThread(CheckHiddenFrames::test); + System.out.println("Started virtual thread: " + thread); + + if (!checkHidden(thread)) { + thread.interrupt(); + throw new RuntimeException("CheckHiddenFrames failed!"); + } + thread.interrupt(); + thread.join(); + } + + static void test() { + sleep(1000000); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/CheckHiddenFrames/libCheckHiddenFrames.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/CheckHiddenFrames/libCheckHiddenFrames.cpp new file mode 100644 index 00000000000..50f930b5731 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/CheckHiddenFrames/libCheckHiddenFrames.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2024, 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 "jvmti.h" +#include "jvmti_common.hpp" + +extern "C" { + +const int MAX_COUNT = 50; +static jvmtiEnv *jvmti = nullptr; + +static char* +get_frame_method_name(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jint depth) { + jmethodID method = nullptr; + jlocation location = 0; + + jvmtiError err = jvmti->GetFrameLocation(thread, 0, &method, &location); + check_jvmti_status(jni, err, "get_method_name_at_depth: error in JVMTI GetFrameLocation"); + + return get_method_name(jvmti, jni, method); +} + +static bool +method_must_be_hidden(char* mname) { + return strcmp(mname, "yield") == 0 || + strcmp(mname, "yield0") == 0; +} + +static jboolean +check_top_frames_location(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) { + jboolean status = JNI_TRUE; + + for (int depth = 0; depth < 2; depth++) { + char* mname = get_frame_method_name(jvmti, jni, thread, depth); + + if (method_must_be_hidden(mname)) { + LOG("Failed: GetFrameLocation returned info for frame expected to be hidden: frame[%d]=%s\n", depth, mname); + status = JNI_FALSE; + } + deallocate(jvmti, jni, mname); + } + return status; +} + +static jboolean +check_top_frames_in_stack_trace(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) { + jboolean status = JNI_TRUE; + jvmtiFrameInfo frameInfo[MAX_COUNT]; + jint count1 = 0; + jint count2 = 0; + + jvmtiError err = jvmti->GetStackTrace(thread, 0, MAX_COUNT, frameInfo, &count1); + check_jvmti_status(jni, err, "check_top_frames_in_stack_trace: error in JVMTI GetStackTrace"); + + for (int depth = 0; depth < 2; depth++) { + char* mname = get_method_name(jvmti, jni, frameInfo[depth].method); + + if (method_must_be_hidden(mname)) { + LOG("Failed: GetStackTrace returned info for frame expected to be hidden: frame[%d]=%s\n", depth, mname); + status = JNI_FALSE; + } + deallocate(jvmti, jni, mname); + } + + err = jvmti->GetFrameCount(thread, &count2); + check_jvmti_status(jni, err, "check_top_frames_in_stack_trace: error in JVMTI GetFrameCount"); + + if (count1 != count2) { + LOG("Failed: frame counts returned by GetStackTrace and GetFrameCount do not match: %d!=%d\n", count1, count2); + status = JNI_FALSE; + } + return status; +} + +JNIEXPORT jboolean JNICALL +Java_CheckHiddenFrames_checkHidden(JNIEnv *jni, jclass clazz, jthread thread) { + jboolean status = JNI_TRUE; + + wait_for_state(jvmti, jni, thread, JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT); + print_stack_trace(jvmti, jni, thread); + + + if (!check_top_frames_location(jvmti, jni, thread)) { + status = JNI_FALSE; + } + if (!check_top_frames_in_stack_trace(jvmti, jni, thread)) { + status = JNI_FALSE; + } + return status; +} + +extern JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + LOG("Agent_OnLoad started\n"); + if (jvm->GetEnv((void **)(&jvmti), JVMTI_VERSION) != JNI_OK) { + return JNI_ERR; + } + LOG("Agent_OnLoad finished\n"); + return JNI_OK; +} + +} // extern "C"