mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
8341273: JVMTI is not properly hiding some continuation related methods
Reviewed-by: alanb, amenkov
This commit is contained in:
parent
520ddac970
commit
60364ef001
15 changed files with 316 additions and 79 deletions
|
@ -926,6 +926,7 @@ public:
|
||||||
_method_ForceInline,
|
_method_ForceInline,
|
||||||
_method_DontInline,
|
_method_DontInline,
|
||||||
_method_ChangesCurrentThread,
|
_method_ChangesCurrentThread,
|
||||||
|
_method_JvmtiHideEvents,
|
||||||
_method_JvmtiMountTransition,
|
_method_JvmtiMountTransition,
|
||||||
_method_InjectedProfile,
|
_method_InjectedProfile,
|
||||||
_method_LambdaForm_Compiled,
|
_method_LambdaForm_Compiled,
|
||||||
|
@ -1830,6 +1831,11 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data,
|
||||||
if (!privileged) break; // only allow in privileged code
|
if (!privileged) break; // only allow in privileged code
|
||||||
return _method_ChangesCurrentThread;
|
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): {
|
case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_JvmtiMountTransition_signature): {
|
||||||
if (_location != _in_method) break; // only allow for methods
|
if (_location != _in_method) break; // only allow for methods
|
||||||
if (!privileged) break; // only allow in privileged code
|
if (!privileged) break; // only allow in privileged code
|
||||||
|
@ -1917,6 +1923,8 @@ void MethodAnnotationCollector::apply_to(const methodHandle& m) {
|
||||||
m->set_dont_inline();
|
m->set_dont_inline();
|
||||||
if (has_annotation(_method_ChangesCurrentThread))
|
if (has_annotation(_method_ChangesCurrentThread))
|
||||||
m->set_changes_current_thread();
|
m->set_changes_current_thread();
|
||||||
|
if (has_annotation(_method_JvmtiHideEvents))
|
||||||
|
m->set_jvmti_hide_events();
|
||||||
if (has_annotation(_method_JvmtiMountTransition))
|
if (has_annotation(_method_JvmtiMountTransition))
|
||||||
m->set_jvmti_mount_transition();
|
m->set_jvmti_mount_transition();
|
||||||
if (has_annotation(_method_InjectedProfile))
|
if (has_annotation(_method_InjectedProfile))
|
||||||
|
|
|
@ -306,6 +306,7 @@ class SerializeClosure;
|
||||||
template(jdk_internal_vm_annotation_Stable_signature, "Ljdk/internal/vm/annotation/Stable;") \
|
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_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;") \
|
template(jdk_internal_vm_annotation_JvmtiMountTransition_signature, "Ljdk/internal/vm/annotation/JvmtiMountTransition;") \
|
||||||
\
|
\
|
||||||
/* Support for JSR 292 & invokedynamic (JDK 1.7 and above) */ \
|
/* Support for JSR 292 & invokedynamic (JDK 1.7 and above) */ \
|
||||||
|
|
|
@ -60,6 +60,7 @@ class ConstMethodFlags {
|
||||||
flag(jvmti_mount_transition , 1 << 18) \
|
flag(jvmti_mount_transition , 1 << 18) \
|
||||||
flag(deprecated , 1 << 19) \
|
flag(deprecated , 1 << 19) \
|
||||||
flag(deprecated_for_removal , 1 << 20) \
|
flag(deprecated_for_removal , 1 << 20) \
|
||||||
|
flag(jvmti_hide_events , 1 << 21) \
|
||||||
/* end of list */
|
/* end of list */
|
||||||
|
|
||||||
#define CM_FLAGS_ENUM_NAME(name, value) _misc_##name = value,
|
#define CM_FLAGS_ENUM_NAME(name, value) _misc_##name = value,
|
||||||
|
|
|
@ -746,6 +746,9 @@ public:
|
||||||
bool changes_current_thread() const { return constMethod()->changes_current_thread(); }
|
bool changes_current_thread() const { return constMethod()->changes_current_thread(); }
|
||||||
void set_changes_current_thread() { constMethod()->set_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(); }
|
bool jvmti_mount_transition() const { return constMethod()->jvmti_mount_transition(); }
|
||||||
void set_jvmti_mount_transition() { constMethod()->set_jvmti_mount_transition(); }
|
void set_jvmti_mount_transition() { constMethod()->set_jvmti_mount_transition(); }
|
||||||
|
|
||||||
|
|
|
@ -584,7 +584,6 @@ JvmtiEnvBase::jvf_for_thread_and_depth(JavaThread* java_thread, jint depth) {
|
||||||
javaVFrame *jvf = java_thread->last_java_vframe(®_map);
|
javaVFrame *jvf = java_thread->last_java_vframe(®_map);
|
||||||
|
|
||||||
jvf = JvmtiEnvBase::check_and_skip_hidden_frames(java_thread, jvf);
|
jvf = JvmtiEnvBase::check_and_skip_hidden_frames(java_thread, jvf);
|
||||||
|
|
||||||
for (int d = 0; jvf != nullptr && d < depth; d++) {
|
for (int d = 0; jvf != nullptr && d < depth; d++) {
|
||||||
jvf = jvf->java_sender();
|
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;
|
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*
|
javaVFrame*
|
||||||
JvmtiEnvBase::check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFrame* jvf) {
|
JvmtiEnvBase::check_and_skip_hidden_frames(bool is_in_VTMS_transition, javaVFrame* jvf) {
|
||||||
// The second condition is needed to hide notification methods.
|
if (jvf == nullptr) {
|
||||||
if (!is_in_VTMS_transition && (jvf == nullptr || !jvf->method()->jvmti_mount_transition())) {
|
return jvf; // empty stack is possible
|
||||||
return jvf; // No frames to skip.
|
|
||||||
}
|
}
|
||||||
// Find jvf with a method annotated with @JvmtiMountTransition.
|
if (jvf->method()->jvmti_mount_transition()) {
|
||||||
for ( ; jvf != nullptr; jvf = jvf->java_sender()) {
|
// Skip frames annotated with @JvmtiMountTransition.
|
||||||
if (jvf->method()->jvmti_mount_transition()) { // Cannot actually appear in an unmounted continuation; they're never frozen.
|
for ( ; jvf != nullptr && jvf->method()->jvmti_mount_transition(); jvf = jvf->java_sender()) {
|
||||||
jvf = jvf->java_sender(); // Skip annotated method.
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (jvf->method()->changes_current_thread()) {
|
} else if (is_in_VTMS_transition) {
|
||||||
break;
|
// 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;
|
return jvf;
|
||||||
}
|
}
|
||||||
|
@ -678,17 +697,6 @@ JvmtiEnvBase::check_and_skip_hidden_frames(JavaThread* jt, javaVFrame* jvf) {
|
||||||
return 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*
|
javaVFrame*
|
||||||
JvmtiEnvBase::get_vthread_jvf(oop vthread) {
|
JvmtiEnvBase::get_vthread_jvf(oop vthread) {
|
||||||
assert(java_lang_VirtualThread::state(vthread) != java_lang_VirtualThread::NEW, "sanity check");
|
assert(java_lang_VirtualThread::state(vthread) != java_lang_VirtualThread::NEW, "sanity check");
|
||||||
|
@ -707,12 +715,13 @@ JvmtiEnvBase::get_vthread_jvf(oop vthread) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
vframeStream vfs(java_thread);
|
vframeStream vfs(java_thread);
|
||||||
|
assert(!java_thread->is_in_VTMS_transition(), "invariant");
|
||||||
jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame();
|
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 {
|
} else {
|
||||||
vframeStream vfs(cont);
|
vframeStream vfs(cont);
|
||||||
jvf = vfs.at_end() ? nullptr : vfs.asJavaVFrame();
|
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;
|
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);
|
bool cthread_with_cont = JvmtiEnvBase::is_cthread_with_continuation(jt);
|
||||||
javaVFrame *jvf = cthread_with_cont ? jt->carrier_last_java_vframe(reg_map_p)
|
javaVFrame *jvf = cthread_with_cont ? jt->carrier_last_java_vframe(reg_map_p)
|
||||||
: jt->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.
|
// Skip hidden frames for carrier threads only.
|
||||||
if (jt->is_in_VTMS_transition()) {
|
jvf = check_and_skip_hidden_frames(jt, jvf);
|
||||||
jvf = check_and_skip_hidden_frames(jt, jvf);
|
|
||||||
}
|
|
||||||
return jvf;
|
return jvf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1332,7 +1339,9 @@ JvmtiEnvBase::set_frame_pop(JvmtiThreadState* state, javaVFrame* jvf, jint depth
|
||||||
if (jvf == nullptr) {
|
if (jvf == nullptr) {
|
||||||
return JVMTI_ERROR_NO_MORE_FRAMES;
|
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;
|
return JVMTI_ERROR_OPAQUE_FRAME;
|
||||||
}
|
}
|
||||||
assert(jvf->frame_pointer() != nullptr, "frame pointer mustn't be null");
|
assert(jvf->frame_pointer() != nullptr, "frame pointer mustn't be null");
|
||||||
|
@ -1989,7 +1998,6 @@ void
|
||||||
JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, jthread target) {
|
JvmtiHandshake::execute(JvmtiUnitedHandshakeClosure* hs_cl, jthread target) {
|
||||||
JavaThread* current = JavaThread::current();
|
JavaThread* current = JavaThread::current();
|
||||||
HandleMark hm(current);
|
HandleMark hm(current);
|
||||||
|
|
||||||
JvmtiVTMSTransitionDisabler disabler(target);
|
JvmtiVTMSTransitionDisabler disabler(target);
|
||||||
ThreadsListHandle tlh(current);
|
ThreadsListHandle tlh(current);
|
||||||
JavaThread* java_thread = nullptr;
|
JavaThread* java_thread = nullptr;
|
||||||
|
|
|
@ -366,9 +366,9 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
|
||||||
static bool get_field_descriptor(Klass* k, jfieldID field, fieldDescriptor* fd);
|
static bool get_field_descriptor(Klass* k, jfieldID field, fieldDescriptor* fd);
|
||||||
|
|
||||||
// check and skip frames hidden in mount/unmount transitions
|
// 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(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(JavaThread* jt, javaVFrame* jvf);
|
||||||
static javaVFrame* check_and_skip_hidden_frames(oop vthread, javaVFrame* jvf);
|
|
||||||
|
|
||||||
// check if virtual thread is not terminated (alive)
|
// check if virtual thread is not terminated (alive)
|
||||||
static bool is_vthread_alive(oop vt);
|
static bool is_vthread_alive(oop vt);
|
||||||
|
|
|
@ -833,11 +833,6 @@ VM_VirtualThreadGetOrSetLocal::VM_VirtualThreadGetOrSetLocal(JvmtiEnv* env, Hand
|
||||||
}
|
}
|
||||||
|
|
||||||
javaVFrame *VM_VirtualThreadGetOrSetLocal::get_java_vframe() {
|
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());
|
JavaThread* java_thread = JvmtiEnvBase::get_JavaThread_or_null(_vthread_h());
|
||||||
bool is_cont_mounted = (java_thread != nullptr);
|
bool is_cont_mounted = (java_thread != nullptr);
|
||||||
|
|
||||||
|
@ -845,22 +840,8 @@ javaVFrame *VM_VirtualThreadGetOrSetLocal::get_java_vframe() {
|
||||||
_result = JVMTI_ERROR_THREAD_NOT_SUSPENDED;
|
_result = JVMTI_ERROR_THREAD_NOT_SUSPENDED;
|
||||||
return nullptr;
|
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;
|
int d = 0;
|
||||||
while ((jvf != nullptr) && (d < _depth)) {
|
while ((jvf != nullptr) && (d < _depth)) {
|
||||||
jvf = jvf->java_sender();
|
jvf = jvf->java_sender();
|
||||||
|
|
|
@ -253,7 +253,7 @@ JvmtiVTMSTransitionDisabler::print_info() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// disable VTMS transitions for one virtual thread
|
// 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)
|
JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread)
|
||||||
: _is_SR(false), _thread(thread)
|
: _is_SR(false), _thread(thread)
|
||||||
{
|
{
|
||||||
|
@ -266,6 +266,17 @@ JvmtiVTMSTransitionDisabler::JvmtiVTMSTransitionDisabler(jthread thread)
|
||||||
if (!sync_protocol_enabled_permanently()) {
|
if (!sync_protocol_enabled_permanently()) {
|
||||||
JvmtiVTMSTransitionDisabler::inc_sync_protocol_enabled_count();
|
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) {
|
if (_thread != nullptr) {
|
||||||
VTMS_transition_disable_for_one(); // disable VTMS transitions for one virtual thread
|
VTMS_transition_disable_for_one(); // disable VTMS transitions for one virtual thread
|
||||||
} else {
|
} else {
|
||||||
|
@ -316,9 +327,8 @@ JvmtiVTMSTransitionDisabler::VTMS_transition_disable_for_one() {
|
||||||
JavaThread* thread = JavaThread::current();
|
JavaThread* thread = JavaThread::current();
|
||||||
HandleMark hm(thread);
|
HandleMark hm(thread);
|
||||||
Handle vth = Handle(thread, JNIHandles::resolve_external_guard(_thread));
|
Handle vth = Handle(thread, JNIHandles::resolve_external_guard(_thread));
|
||||||
if (!java_lang_VirtualThread::is_instance(vth())) {
|
assert(java_lang_VirtualThread::is_instance(vth()), "sanity check");
|
||||||
return; // no-op if _thread is not a virtual thread
|
|
||||||
}
|
|
||||||
MonitorLocker ml(JvmtiVTMSTransition_lock);
|
MonitorLocker ml(JvmtiVTMSTransition_lock);
|
||||||
|
|
||||||
while (_SR_mode) { // suspender or resumer is a JvmtiVTMSTransitionDisabler monopolist
|
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)
|
JvmtiVTSuspender::is_vthread_suspended(thread_id)
|
||||||
) {
|
) {
|
||||||
// Block while transitions are disabled or there are suspend requests.
|
// Block while transitions are disabled or there are suspend requests.
|
||||||
if (ml.wait(10)) {
|
if (ml.wait(200)) {
|
||||||
attempts--;
|
attempts--;
|
||||||
}
|
}
|
||||||
DEBUG_ONLY(if (attempts == 0) break;)
|
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))
|
(is_mount && JvmtiVTSuspender::is_vthread_suspended(thread_id))
|
||||||
) {
|
) {
|
||||||
// Block while there are suspend requests.
|
// Block while there are suspend requests.
|
||||||
if (ml.wait(10)) {
|
if (ml.wait(200)) {
|
||||||
attempts--;
|
attempts--;
|
||||||
}
|
}
|
||||||
DEBUG_ONLY(if (attempts == 0) break;)
|
DEBUG_ONLY(if (attempts == 0) break;)
|
||||||
|
|
|
@ -56,6 +56,7 @@ import jdk.internal.vm.ThreadContainers;
|
||||||
import jdk.internal.vm.annotation.ChangesCurrentThread;
|
import jdk.internal.vm.annotation.ChangesCurrentThread;
|
||||||
import jdk.internal.vm.annotation.Hidden;
|
import jdk.internal.vm.annotation.Hidden;
|
||||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||||
|
import jdk.internal.vm.annotation.JvmtiHideEvents;
|
||||||
import jdk.internal.vm.annotation.JvmtiMountTransition;
|
import jdk.internal.vm.annotation.JvmtiMountTransition;
|
||||||
import jdk.internal.vm.annotation.ReservedStackAccess;
|
import jdk.internal.vm.annotation.ReservedStackAccess;
|
||||||
import sun.nio.ch.Interruptible;
|
import sun.nio.ch.Interruptible;
|
||||||
|
@ -213,8 +214,14 @@ final class VirtualThread extends BaseVirtualThread {
|
||||||
private static Runnable wrap(VirtualThread vthread, Runnable task) {
|
private static Runnable wrap(VirtualThread vthread, Runnable task) {
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
@Hidden
|
@Hidden
|
||||||
|
@JvmtiHideEvents
|
||||||
public void run() {
|
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) {
|
private void run(Runnable task) {
|
||||||
assert Thread.currentThread() == this && state == RUNNING;
|
assert Thread.currentThread() == this && state == RUNNING;
|
||||||
|
|
||||||
// notify JVMTI, may post VirtualThreadStart event
|
|
||||||
notifyJvmtiStart();
|
|
||||||
|
|
||||||
// emit JFR event if enabled
|
// emit JFR event if enabled
|
||||||
if (VirtualThreadStartEvent.isTurnedOn()) {
|
if (VirtualThreadStartEvent.isTurnedOn()) {
|
||||||
var event = new VirtualThreadStartEvent();
|
var event = new VirtualThreadStartEvent();
|
||||||
|
@ -405,20 +409,14 @@ final class VirtualThread extends BaseVirtualThread {
|
||||||
} catch (Throwable exc) {
|
} catch (Throwable exc) {
|
||||||
dispatchUncaughtException(exc);
|
dispatchUncaughtException(exc);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
// pop any remaining scopes from the stack, this may block
|
||||||
// pop any remaining scopes from the stack, this may block
|
StackableScope.popAll();
|
||||||
StackableScope.popAll();
|
|
||||||
|
|
||||||
// emit JFR event if enabled
|
// emit JFR event if enabled
|
||||||
if (VirtualThreadEndEvent.isTurnedOn()) {
|
if (VirtualThreadEndEvent.isTurnedOn()) {
|
||||||
var event = new VirtualThreadEndEvent();
|
var event = new VirtualThreadEndEvent();
|
||||||
event.javaThreadId = threadId();
|
event.javaThreadId = threadId();
|
||||||
event.commit();
|
event.commit();
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// notify JVMTI, may post VirtualThreadEnd event
|
|
||||||
notifyJvmtiEnd();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import java.util.function.Supplier;
|
||||||
import jdk.internal.access.JavaLangAccess;
|
import jdk.internal.access.JavaLangAccess;
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
import jdk.internal.vm.annotation.Hidden;
|
import jdk.internal.vm.annotation.Hidden;
|
||||||
|
import jdk.internal.vm.annotation.JvmtiHideEvents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A one-shot delimited continuation.
|
* A one-shot delimited continuation.
|
||||||
|
@ -305,6 +306,7 @@ public class Continuation {
|
||||||
@Hidden
|
@Hidden
|
||||||
@DontInline
|
@DontInline
|
||||||
@IntrinsicCandidate
|
@IntrinsicCandidate
|
||||||
|
@JvmtiHideEvents
|
||||||
private static void enter(Continuation c, boolean isContinue) {
|
private static void enter(Continuation c, boolean isContinue) {
|
||||||
// This method runs in the "entry frame".
|
// This method runs in the "entry frame".
|
||||||
// A yield jumps to this method's caller as if returning from this method.
|
// A yield jumps to this method's caller as if returning from this method.
|
||||||
|
@ -316,6 +318,7 @@ public class Continuation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
|
@JvmtiHideEvents
|
||||||
private void enter0() {
|
private void enter0() {
|
||||||
target.run();
|
target.run();
|
||||||
}
|
}
|
||||||
|
@ -340,6 +343,7 @@ public class Continuation {
|
||||||
* @throws IllegalStateException if not currently in the given {@code scope},
|
* @throws IllegalStateException if not currently in the given {@code scope},
|
||||||
*/
|
*/
|
||||||
@Hidden
|
@Hidden
|
||||||
|
@JvmtiHideEvents
|
||||||
public static boolean yield(ContinuationScope scope) {
|
public static boolean yield(ContinuationScope scope) {
|
||||||
Continuation cont = JLA.getContinuation(currentCarrierThread());
|
Continuation cont = JLA.getContinuation(currentCarrierThread());
|
||||||
Continuation c;
|
Continuation c;
|
||||||
|
@ -352,6 +356,7 @@ public class Continuation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hidden
|
@Hidden
|
||||||
|
@JvmtiHideEvents
|
||||||
private boolean yield0(ContinuationScope scope, Continuation child) {
|
private boolean yield0(ContinuationScope scope, Continuation child) {
|
||||||
preempted = false;
|
preempted = false;
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
}
|
|
@ -28,8 +28,11 @@ package jdk.internal.vm.annotation;
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method is annotated as "jvmti mount transition" if it starts
|
* A method may be annotated with JvmtiMountTransition to hint
|
||||||
* or ends virtual thread mount state transition (VTMS transition).
|
* 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
|
* @implNote
|
||||||
* This annotation is only used for VirtualThread methods.
|
* This annotation is only used for VirtualThread methods.
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class framecnt01 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is too fragile, implementation can change at any time.
|
// this is too fragile, implementation can change at any time.
|
||||||
checkFrames(vThread1, false, 13);
|
checkFrames(vThread1, false, 11);
|
||||||
LockSupport.unpark(vThread1);
|
LockSupport.unpark(vThread1);
|
||||||
vThread1.join();
|
vThread1.join();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <string.h>
|
||||||
|
#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"
|
Loading…
Add table
Add a link
Reference in a new issue