8341273: JVMTI is not properly hiding some continuation related methods
Reviewed-by: alanb, amenkov
This commit is contained in:
parent
520ddac970
commit
60364ef001
@ -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))
|
||||
|
@ -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) */ \
|
||||
|
@ -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,
|
||||
|
@ -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(); }
|
||||
|
||||
|
@ -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;
|
||||
|
@ -366,9 +366,9 @@ class JvmtiEnvBase : public CHeapObj<mtInternal> {
|
||||
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);
|
||||
|
@ -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();
|
||||
|
@ -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;)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.*;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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…
x
Reference in New Issue
Block a user