From 47a32e8c4dbe0e028c2f68ee51c20422ebac146b Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Sat, 9 Aug 2025 13:28:22 -0700 Subject: [PATCH 01/26] 8365192: post_meth_exit should be in vm state when calling get_jvmti_thread_state --- src/hotspot/share/prims/jvmtiExport.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index a82ad2db6b69c..9fc6a54ad78ad 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -420,6 +420,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { JvmtiThreadState* JvmtiExport::get_jvmti_thread_state(JavaThread *thread, bool allow_suspend) { assert(thread == JavaThread::current(), "must be current thread"); + assert(thread->thread_state() == _thread_in_vm, "thread should be in vm"); if (thread->is_vthread_mounted() && thread->jvmti_thread_state() == nullptr) { JvmtiEventController::thread_started(thread); if (allow_suspend && thread->is_suspended()) { @@ -1831,7 +1832,11 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur HandleMark hm(thread); methodHandle mh(thread, method); - JvmtiThreadState *state = get_jvmti_thread_state(thread); + JvmtiThreadState *state = nullptr; + JavaThread* current = thread; // for JRT_BLOCK + JRT_BLOCK + state = get_jvmti_thread_state(thread); + JRT_BLOCK_END if (state == nullptr || !state->is_interp_only_mode()) { // for any thread that actually wants method exit, interp_only_mode is set @@ -1867,7 +1872,6 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur // Deferred transition to VM, so we can stash away the return oop before GC // Note that this transition is not needed when throwing an exception, because // there is no oop to retain. - JavaThread* current = thread; // for JRT_BLOCK JRT_BLOCK post_method_exit_inner(thread, mh, state, exception_exit, current_frame, value); JRT_BLOCK_END From 7cce73e34aba47be83bd3cc39a7351609f11119c Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 12 Aug 2025 23:15:51 -0700 Subject: [PATCH 02/26] simplified after feedback --- src/hotspot/share/prims/jvmtiExport.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 9fc6a54ad78ad..a850130eae210 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1833,11 +1833,10 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur methodHandle mh(thread, method); JvmtiThreadState *state = nullptr; - JavaThread* current = thread; // for JRT_BLOCK - JRT_BLOCK - state = get_jvmti_thread_state(thread); - JRT_BLOCK_END - + { + ThreadInVMfromJava __tiv(thread); + state = get_jvmti_thread_state(thread); + } if (state == nullptr || !state->is_interp_only_mode()) { // for any thread that actually wants method exit, interp_only_mode is set return; @@ -1872,6 +1871,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur // Deferred transition to VM, so we can stash away the return oop before GC // Note that this transition is not needed when throwing an exception, because // there is no oop to retain. + JavaThread* current = thread; // for JRT_BLOCK JRT_BLOCK post_method_exit_inner(thread, mh, state, exception_exit, current_frame, value); JRT_BLOCK_END From 977c9eb4036d2f3cfb761ba6d1b16a044c07a3b6 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 13 Aug 2025 07:33:39 -0700 Subject: [PATCH 03/26] fixed name --- src/hotspot/share/prims/jvmtiExport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index a850130eae210..5cae8f271242e 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -382,7 +382,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { JavaThread* current_thread = JavaThread::current(); // transition code: native to VM MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current_thread)); - ThreadInVMfromNative __tiv(current_thread); + ThreadInVMfromNative tiv(current_thread); VM_ENTRY_BASE(jvmtiEnv*, JvmtiExport::get_jvmti_interface, current_thread) DEBUG_ONLY(VMNativeEntryWrapper __vew;) From 31046bae5e966c9849ab19f0d33796daec75ee23 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 13 Aug 2025 08:05:00 -0700 Subject: [PATCH 04/26] wong phase --- src/hotspot/share/prims/jvmtiExport.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 5cae8f271242e..61d926076f924 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -382,7 +382,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { JavaThread* current_thread = JavaThread::current(); // transition code: native to VM MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current_thread)); - ThreadInVMfromNative tiv(current_thread); + ThreadInVMfromNative _tiv(current_thread); VM_ENTRY_BASE(jvmtiEnv*, JvmtiExport::get_jvmti_interface, current_thread) DEBUG_ONLY(VMNativeEntryWrapper __vew;) @@ -1834,7 +1834,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur JvmtiThreadState *state = nullptr; { - ThreadInVMfromJava __tiv(thread); + ThreadInVMfromJava tiv(thread); state = get_jvmti_thread_state(thread); } if (state == nullptr || !state->is_interp_only_mode()) { From 255c0ba808d912fb06faef866c9bd9ffc80ccbed Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 13 Aug 2025 08:05:37 -0700 Subject: [PATCH 05/26] added _ --- src/hotspot/share/prims/jvmtiExport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 61d926076f924..757d8f16eeb52 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -382,7 +382,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { JavaThread* current_thread = JavaThread::current(); // transition code: native to VM MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current_thread)); - ThreadInVMfromNative _tiv(current_thread); + ThreadInVMfromNative __tiv(current_thread); VM_ENTRY_BASE(jvmtiEnv*, JvmtiExport::get_jvmti_interface, current_thread) DEBUG_ONLY(VMNativeEntryWrapper __vew;) From 7c2ac4eb2729adf73cbd33f72d0bf5baccfe375a Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Mon, 18 Aug 2025 21:22:46 -0700 Subject: [PATCH 06/26] Test added. --- .../events/MethodExit/ExceptionOccurred.java | 57 ++++++++++ .../libExceptionOccurred.cpp | 105 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java new file mode 100644 index 0000000000000..270d680d09607 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, 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 Test verifies that MethodExit event is correctly posted + * if exception occured in the current thread. + * + * @bug 8365192 + * @run main/othervm/native -agentlib:ExceptionOccurred ExceptionOccurred + */ +public class ExceptionOccurred { + + private static native void enable(); + private static native void disableAndCheck(); + + static String exceptionExit() { + throw new RuntimeException("MyRuntimeException"); + } + + + // Called from ExcptionExit MethodExit callback via JNI + static String upCall() { + return "MyNewString"; + } + + public static void main(String[] args) throws InterruptedException { + System.loadLibrary("ExceptionOccurred"); + try { + enable(); + exceptionExit(); + disableAndCheck(); + } catch (RuntimeException e){ + //expected + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp new file mode 100644 index 0000000000000..801778cbef95c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025, 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 "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +jvmtiEnv* jvmti_env; + +bool method_exit_posted = false; +static void JNICALL +cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, + jboolean was_popped_by_exception, jvalue return_value) { + const char * mname = get_method_name(jvmti, jni, method); + if (strcmp("upCall", mname) == 0) { + if (was_popped_by_exception) { + fatal(jni, "The method's was_popped_by_exception value is incorrect."); + } + jstring upcall_result = (jstring) return_value.l; + const char *str = jni->GetStringUTFChars(upcall_result, nullptr); + if (str == nullptr) { + fatal(jni ,"Failed to convert Java string to C string."); + } + if (strcmp("MyNewString", str) != 0) { + fatal(jni, "The upCall result value is incorrect."); + } + method_exit_posted = true; + } + if (strcmp("exceptionExit", mname) != 0) { + return; + } + + jclass main_class = jni->FindClass("ExceptionOccurred"); + if (main_class == nullptr) { + fatal(jni,"Can't find ExceptionOccurred class."); + return; + } + + jmethodID upcall_method = jni->GetStaticMethodID(main_class, "upCall", "()Ljava/lang/String;"); + if (upcall_method == nullptr) { + fatal(jni,"Can't find upCall method."); + } +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + jvmtiEnv *jvmti = nullptr; + jint res = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_21); + if (res != JNI_OK) { + return JNI_ERR; + } + jvmtiError err = JVMTI_ERROR_NONE; + jvmtiCapabilities capabilities; + (void) memset(&capabilities, 0, sizeof (capabilities)); + capabilities.can_generate_method_exit_events = true; + err = jvmti->AddCapabilities(&capabilities); + check_jvmti_error(err, "AddCapabilities"); + jvmtiEventCallbacks callbacks; + (void) memset(&callbacks, 0, sizeof (callbacks)); + callbacks.MethodExit = &cbMethodExit; + err = jvmti->SetEventCallbacks(&callbacks, (int) sizeof (jvmtiEventCallbacks)); + check_jvmti_error(err, "SetEventCallbacks"); + jvmti_env = jvmti; + return JNI_OK; +} + + +extern "C" { +JNIEXPORT void JNICALL +Java_ExceptionOccurred_enable(JNIEnv *jni, jclass clazz) { + jthread thread = get_current_thread(jvmti_env, jni); + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, thread); +} + + +JNIEXPORT void JNICALL +Java_ExceptionOccurred_disableAndCheck(JNIEnv *jni, jclass clazz) { + jthread thread = get_current_thread(jvmti_env, jni); + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_EXIT, thread); + if (!method_exit_posted) { + fatal(jni, "Failed to post method exit event."); + } +} + +} From d9d21af656d990add52a2934edc976296230db3c Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Mon, 18 Aug 2025 22:49:25 -0700 Subject: [PATCH 07/26] The oop preservation and exception handling has been fixed. --- src/hotspot/share/prims/jvmtiExport.cpp | 50 +++++++++++-------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 757d8f16eeb52..85734eaacb8b9 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1831,52 +1831,46 @@ void JvmtiExport::post_method_entry(JavaThread *thread, Method* method, frame cu void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame current_frame) { HandleMark hm(thread); methodHandle mh(thread, method); + oop oop_result; + Handle result; + jvalue value; + value.l = 0L; + // post_method_exist is called only when the method is not exit because of + // exception so result should be always initialized. + // At this point we only have the address of a "raw result" and + // we just call into the interpreter to convert this into a jvalue. + // Additionally, the result oop should be preserved while the thread is in java. + BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); - JvmtiThreadState *state = nullptr; + if (is_reference_type(type)) { + result = Handle(thread, oop_result); + } + // Deferred transition to VM, so we can stash away the return oop before GC + // Note that this transition is not needed when throwing an exception, because + // there is no oop to retain. + JvmtiThreadState *state; { ThreadInVMfromJava tiv(thread); state = get_jvmti_thread_state(thread); } + if (state == nullptr || !state->is_interp_only_mode()) { // for any thread that actually wants method exit, interp_only_mode is set return; } - - // return a flag when a method terminates by throwing an exception - // i.e. if an exception is thrown and it's not caught by the current method - bool exception_exit = state->is_exception_detected() && !state->is_exception_caught(); - Handle result; - jvalue value; - value.j = 0L; - - if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { - // if the method hasn't been popped because of an exception then we populate - // the return_value parameter for the callback. At this point we only have - // the address of a "raw result" and we just call into the interpreter to - // convert this into a jvalue. - if (!exception_exit) { - oop oop_result; - BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); - if (is_reference_type(type)) { - result = Handle(thread, oop_result); - value.l = JNIHandles::make_local(thread, result()); - } - } + if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT) && is_reference_type(type)) { + value.l = JNIHandles::make_local(thread, result()); } - // Do not allow NotifyFramePop to add new FramePop event request at // depth 0 as it is already late in the method exiting dance. state->set_top_frame_is_exiting(); - // Deferred transition to VM, so we can stash away the return oop before GC - // Note that this transition is not needed when throwing an exception, because - // there is no oop to retain. JavaThread* current = thread; // for JRT_BLOCK JRT_BLOCK - post_method_exit_inner(thread, mh, state, exception_exit, current_frame, value); + post_method_exit_inner(thread, mh, state,false, current_frame, value); JRT_BLOCK_END - // The JRT_BLOCK_END can safepoint in ThreadInVMfromJava desctructor. Now it is safe to allow + // The JRT_BLOCK_END can safepoint in ThreadInVMfromJava destructor. Now it is safe to allow // adding FramePop event requests as no safepoint can happen before removing activation. state->clr_top_frame_is_exiting(); From 05e797c15d66134a433b26de0902a8af5f7f216e Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 16:13:33 -0700 Subject: [PATCH 08/26] test fixed. --- .../jvmti/events/MethodExit/ExceptionOccurred.java | 3 +-- .../ExceptionOccurred/libExceptionOccurred.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java index 270d680d09607..2ef417b97b5c0 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java @@ -49,9 +49,8 @@ public static void main(String[] args) throws InterruptedException { try { enable(); exceptionExit(); - disableAndCheck(); } catch (RuntimeException e){ - //expected + disableAndCheck(); } } } diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp index 801778cbef95c..b53a90d5b6fe2 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp @@ -60,6 +60,16 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, if (upcall_method == nullptr) { fatal(jni,"Can't find upCall method."); } + jstring upcall_result = (jstring) jni->CallStaticObjectMethod(main_class, upcall_method); + const char *str = jni->GetStringUTFChars(upcall_result, NULL); + if (str == NULL) { + fatal(jni ,"Failed to convert Java string to C string."); + return; + } + if (strcmp("MyNewString", str) != 0) { + fatal(jni, "The upCall result value is incorrect."); + } + jni->ReleaseStringUTFChars(upcall_result, str); } JNIEXPORT jint JNICALL From 2e96eb82a7b5daf0d033a709ffbc1f10e9488beb Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 16:14:08 -0700 Subject: [PATCH 09/26] added assertion and removed comment. --- src/hotspot/share/prims/jvmtiExport.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 85734eaacb8b9..8dd767c8dfaf2 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1842,12 +1842,13 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur // Additionally, the result oop should be preserved while the thread is in java. BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); + assert(type == T_VOID || current_frame.interpreter_frame_expression_stack_size() > 0, + "Stack shouldn't ne empty"); + if (is_reference_type(type)) { result = Handle(thread, oop_result); } // Deferred transition to VM, so we can stash away the return oop before GC - // Note that this transition is not needed when throwing an exception, because - // there is no oop to retain. JvmtiThreadState *state; { ThreadInVMfromJava tiv(thread); From 32b08587086de81f5c78391d4e8281983af1b839 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 16:18:55 -0700 Subject: [PATCH 10/26] moved JRT_BLOCK --- src/hotspot/share/prims/jvmtiExport.cpp | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 8dd767c8dfaf2..ee6c97f656077 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1843,31 +1843,28 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); assert(type == T_VOID || current_frame.interpreter_frame_expression_stack_size() > 0, - "Stack shouldn't ne empty"); + "Stack shouldn't be empty"); if (is_reference_type(type)) { result = Handle(thread, oop_result); } - // Deferred transition to VM, so we can stash away the return oop before GC JvmtiThreadState *state; - { - ThreadInVMfromJava tiv(thread); + // Deferred transition to VM, so we can stash away the return oop before GC + JavaThread* current = thread; // for JRT_BLOCK + JRT_BLOCK state = get_jvmti_thread_state(thread); - } - if (state == nullptr || !state->is_interp_only_mode()) { - // for any thread that actually wants method exit, interp_only_mode is set - return; - } - if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT) && is_reference_type(type)) { - value.l = JNIHandles::make_local(thread, result()); - } - // Do not allow NotifyFramePop to add new FramePop event request at - // depth 0 as it is already late in the method exiting dance. - state->set_top_frame_is_exiting(); + if (state == nullptr || !state->is_interp_only_mode()) { + // for any thread that actually wants method exit, interp_only_mode is set + return; + } + if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT) && is_reference_type(type)) { + value.l = JNIHandles::make_local(thread, result()); + } + // Do not allow NotifyFramePop to add new FramePop event request at + // depth 0 as it is already late in the method exiting dance. + state->set_top_frame_is_exiting(); - JavaThread* current = thread; // for JRT_BLOCK - JRT_BLOCK post_method_exit_inner(thread, mh, state,false, current_frame, value); JRT_BLOCK_END From 0008f725d245f69489b3d58bc99da991dc4a85ac Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 21:54:38 -0700 Subject: [PATCH 11/26] NULL replaced --- .../MethodExit/ExceptionOccurred/libExceptionOccurred.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp index b53a90d5b6fe2..68758ef0432f7 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp @@ -61,8 +61,8 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, fatal(jni,"Can't find upCall method."); } jstring upcall_result = (jstring) jni->CallStaticObjectMethod(main_class, upcall_method); - const char *str = jni->GetStringUTFChars(upcall_result, NULL); - if (str == NULL) { + const char *str = jni->GetStringUTFChars(upcall_result, nullptr); + if (str == nullptr) { fatal(jni ,"Failed to convert Java string to C string."); return; } From 320b93ebee042fa4d9c5a9ebe32cfd10d508c42b Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 22:44:53 -0700 Subject: [PATCH 12/26] Update test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java Co-authored-by: David Holmes <62092539+dholmes-ora@users.noreply.github.com> --- .../jvmti/events/MethodExit/ExceptionOccurred.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java index 2ef417b97b5c0..b470103c0a395 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java @@ -39,7 +39,7 @@ static String exceptionExit() { } - // Called from ExcptionExit MethodExit callback via JNI + // Called from ExceptionExit MethodExit callback via JNI static String upCall() { return "MyNewString"; } From 6960b1aa2a54d551af85a445ba4cf9fb017cee27 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 23:34:07 -0700 Subject: [PATCH 13/26] added comments to the test --- .../ExceptionOccurred/libExceptionOccurred.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp index 68758ef0432f7..44a504a374785 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp @@ -28,6 +28,12 @@ jvmtiEnv* jvmti_env; bool method_exit_posted = false; +// This method exit callback actually works only for 2 methods: +// 1) for ExceptionExit it verifies that method exit +// has been popped by exception and call 'upCall' mthod using JNI. +// 2) for upCall method it verifies that event has correct +// return value and was not popped by execption. +// The event callback just exits for all other methods. static void JNICALL cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, jboolean was_popped_by_exception, jvalue return_value) { @@ -49,17 +55,21 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, if (strcmp("exceptionExit", mname) != 0) { return; } - + if (!was_popped_by_exception) { + fatal(jni, "Should have was_popped_by_esxception = true."); + } jclass main_class = jni->FindClass("ExceptionOccurred"); if (main_class == nullptr) { fatal(jni,"Can't find ExceptionOccurred class."); return; } - - jmethodID upcall_method = jni->GetStaticMethodID(main_class, "upCall", "()Ljava/lang/String;"); + jmethodID upcall_method = jni->GetStaticMethodID(main_class, + "upCall", "()Ljava/lang/String;"); if (upcall_method == nullptr) { fatal(jni,"Can't find upCall method."); } + // Call 'upCall' method while current thread has exception + // that has been thrown but hasn't been caught yet. jstring upcall_result = (jstring) jni->CallStaticObjectMethod(main_class, upcall_method); const char *str = jni->GetStringUTFChars(upcall_result, nullptr); if (str == nullptr) { From 67d87dbb93f18c916f79b083c207e710f2dcc6b1 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Tue, 19 Aug 2025 23:40:59 -0700 Subject: [PATCH 14/26] Update src/hotspot/share/prims/jvmtiExport.cpp Co-authored-by: David Holmes <62092539+dholmes-ora@users.noreply.github.com> --- src/hotspot/share/prims/jvmtiExport.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index ee6c97f656077..e220bb6a9056a 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1835,8 +1835,8 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur Handle result; jvalue value; value.l = 0L; - // post_method_exist is called only when the method is not exit because of - // exception so result should be always initialized. + // post_method_exit is only called when the method exits normally, + // so result should be always initialized. // At this point we only have the address of a "raw result" and // we just call into the interpreter to convert this into a jvalue. // Additionally, the result oop should be preserved while the thread is in java. From 106e5a1f31a316f43aa9b4fefee5b369c3d3aa05 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 20 Aug 2025 07:39:41 -0700 Subject: [PATCH 15/26] Update test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp Co-authored-by: David Holmes <62092539+dholmes-ora@users.noreply.github.com> --- .../MethodExit/ExceptionOccurred/libExceptionOccurred.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp index 44a504a374785..db1f039d26e81 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp @@ -32,7 +32,7 @@ bool method_exit_posted = false; // 1) for ExceptionExit it verifies that method exit // has been popped by exception and call 'upCall' mthod using JNI. // 2) for upCall method it verifies that event has correct -// return value and was not popped by execption. +// return value and was not popped by exception. // The event callback just exits for all other methods. static void JNICALL cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, From ce132751a43ae046f89117e7db297645935fd40c Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 20 Aug 2025 08:50:40 -0700 Subject: [PATCH 16/26] test renamed. --- .../TestMethodExitWithPendingException.java} | 6 +++--- .../libTestMethodExitWithPendingException.cpp} | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) rename test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/{ExceptionOccurred.java => PendingException/TestMethodExitWithPendingException.java} (88%) rename test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/{ExceptionOccurred/libExceptionOccurred.cpp => PendingException/libTestMethodExitWithPendingException.cpp} (93%) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java similarity index 88% rename from test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java rename to test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java index b470103c0a395..2ed5c1970c9bb 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java @@ -27,9 +27,9 @@ * if exception occured in the current thread. * * @bug 8365192 - * @run main/othervm/native -agentlib:ExceptionOccurred ExceptionOccurred + * @run main/othervm/native -agentlib:TestMethodExitWithPendingException TestMethodExitWithPendingException */ -public class ExceptionOccurred { +public class TestMethodExitWithPendingException { private static native void enable(); private static native void disableAndCheck(); @@ -45,7 +45,7 @@ static String upCall() { } public static void main(String[] args) throws InterruptedException { - System.loadLibrary("ExceptionOccurred"); + System.loadLibrary("TestMethodExitWithPendingException"); try { enable(); exceptionExit(); diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp similarity index 93% rename from test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp rename to test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp index db1f039d26e81..45eee52f7b815 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/ExceptionOccurred/libExceptionOccurred.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp @@ -58,9 +58,9 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, if (!was_popped_by_exception) { fatal(jni, "Should have was_popped_by_esxception = true."); } - jclass main_class = jni->FindClass("ExceptionOccurred"); + jclass main_class = jni->FindClass("TestMethodExitWithPendingException"); if (main_class == nullptr) { - fatal(jni,"Can't find ExceptionOccurred class."); + fatal(jni,"Can't find TestMethodExitWithPendingException class."); return; } jmethodID upcall_method = jni->GetStaticMethodID(main_class, @@ -107,14 +107,14 @@ Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { extern "C" { JNIEXPORT void JNICALL -Java_ExceptionOccurred_enable(JNIEnv *jni, jclass clazz) { +Java_TestMethodExitWithPendingException_enable(JNIEnv *jni, jclass clazz) { jthread thread = get_current_thread(jvmti_env, jni); jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, thread); } JNIEXPORT void JNICALL -Java_ExceptionOccurred_disableAndCheck(JNIEnv *jni, jclass clazz) { +Java_TestMethodExitWithPendingException_disableAndCheck(JNIEnv *jni, jclass clazz) { jthread thread = get_current_thread(jvmti_env, jni); jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_EXIT, thread); if (!method_exit_posted) { From d56e353681948efe696e6ab0f4db55f292030f17 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 20 Aug 2025 09:44:53 -0700 Subject: [PATCH 17/26] fixed comment. --- .../PendingException/TestMethodExitWithPendingException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java index 2ed5c1970c9bb..10bbd79fc52ae 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java @@ -24,7 +24,7 @@ /* * @test * @summary Test verifies that MethodExit event is correctly posted - * if exception occured in the current thread. + * if method is called while there is a pending exception on this thread. * * @bug 8365192 * @run main/othervm/native -agentlib:TestMethodExitWithPendingException TestMethodExitWithPendingException From a659dc2c59320aae01aceb0579d6216e19a6df0e Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 21 Aug 2025 08:24:41 -0700 Subject: [PATCH 18/26] updated to fix 8365937 --- src/hotspot/share/prims/jvmtiExport.cpp | 57 ++++++------ .../TestPoppedByException.java | 50 +++++++++++ .../libTestPoppedByException.cpp | 86 +++++++++++++++++++ 3 files changed, 163 insertions(+), 30 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/TestPoppedByException.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index e220bb6a9056a..af8d15fae5b5a 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -420,7 +420,6 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { JvmtiThreadState* JvmtiExport::get_jvmti_thread_state(JavaThread *thread, bool allow_suspend) { assert(thread == JavaThread::current(), "must be current thread"); - assert(thread->thread_state() == _thread_in_vm, "thread should be in vm"); if (thread->is_vthread_mounted() && thread->jvmti_thread_state() == nullptr) { JvmtiEventController::thread_started(thread); if (allow_suspend && thread->is_suspended()) { @@ -1831,44 +1830,42 @@ void JvmtiExport::post_method_entry(JavaThread *thread, Method* method, frame cu void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame current_frame) { HandleMark hm(thread); methodHandle mh(thread, method); - oop oop_result; - Handle result; - jvalue value; - value.l = 0L; - // post_method_exit is only called when the method exits normally, - // so result should be always initialized. - // At this point we only have the address of a "raw result" and - // we just call into the interpreter to convert this into a jvalue. - // Additionally, the result oop should be preserved while the thread is in java. - BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); - assert(type == T_VOID || current_frame.interpreter_frame_expression_stack_size() > 0, - "Stack shouldn't be empty"); + JvmtiThreadState *state = get_jvmti_thread_state(thread); - if (is_reference_type(type)) { - result = Handle(thread, oop_result); + if (state == nullptr || !state->is_interp_only_mode()) { + // for any thread that actually wants method exit, interp_only_mode is set + return; } - JvmtiThreadState *state; - // Deferred transition to VM, so we can stash away the return oop before GC - JavaThread* current = thread; // for JRT_BLOCK - JRT_BLOCK - state = get_jvmti_thread_state(thread); - if (state == nullptr || !state->is_interp_only_mode()) { - // for any thread that actually wants method exit, interp_only_mode is set - return; - } - if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT) && is_reference_type(type)) { + Handle result; + jvalue value; + value.j = 0L; + + if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { + // At this point we only have the address of a "raw result" and + // we just call into the interpreter to convert this into a jvalue. + oop oop_result; + BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); + if (is_reference_type(type)) { + result = Handle(thread, oop_result); value.l = JNIHandles::make_local(thread, result()); } - // Do not allow NotifyFramePop to add new FramePop event request at - // depth 0 as it is already late in the method exiting dance. - state->set_top_frame_is_exiting(); + } - post_method_exit_inner(thread, mh, state,false, current_frame, value); + // Do not allow NotifyFramePop to add new FramePop event request at + // depth 0 as it is already late in the method exiting dance. + state->set_top_frame_is_exiting(); + + // Deferred transition to VM, so we can stash away the return oop before GC + // Note that this transition is not needed when throwing an exception, because + // there is no oop to retain. + JavaThread* current = thread; // for JRT_BLOCK + JRT_BLOCK + post_method_exit_inner(thread, mh, state, false, current_frame, value); JRT_BLOCK_END - // The JRT_BLOCK_END can safepoint in ThreadInVMfromJava destructor. Now it is safe to allow + // The JRT_BLOCK_END can safepoint in ThreadInVMfromJava desctructor. Now it is safe to allow // adding FramePop event requests as no safepoint can happen before removing activation. state->clr_top_frame_is_exiting(); diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/TestPoppedByException.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/TestPoppedByException.java new file mode 100644 index 0000000000000..2ea3e0fc27b08 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/TestPoppedByException.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, 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 + * @run main/othervm/native -agentlib:TestPoppedByException TestPoppedByException + */ +public class TestPoppedByException { + + private static native void enable(); + private static native void disableAndCheck(); + + static String exceptionExit() { + throw new RuntimeException("MyRuntimeException"); + } + + static String exceptionExitOuter() { + return exceptionExit(); + } + + public static void main(String[] args) throws InterruptedException { + System.loadLibrary("TestPoppedByException"); + try { + enable(); + exceptionExitOuter(); + } catch (RuntimeException e){ + disableAndCheck(); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp new file mode 100644 index 0000000000000..864b871b58c62 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 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 "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +jvmtiEnv* jvmti_env; +bool method_exit_posted = false; +static void JNICALL +cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, + jboolean was_popped_by_exception, jvalue return_value) { + const char * mname = get_method_name(jvmti, jni, method); + if (strcmp("exceptionExitOuter", mname) == 0) { + if (!was_popped_by_exception) { + fatal(jni, "The method's was_popped_by_exception value is incorrect."); + } + if (return_value.l != nullptr) { + fatal(jni ,"return_value should be nullptr."); + } + method_exit_posted = true; + } +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + jvmtiEnv *jvmti = nullptr; + jint res = vm->GetEnv((void **) &jvmti, JVMTI_VERSION_21); + if (res != JNI_OK) { + return JNI_ERR; + } + jvmtiError err = JVMTI_ERROR_NONE; + jvmtiCapabilities capabilities; + (void) memset(&capabilities, 0, sizeof (capabilities)); + capabilities.can_generate_method_exit_events = true; + err = jvmti->AddCapabilities(&capabilities); + check_jvmti_error(err, "AddCapabilities"); + jvmtiEventCallbacks callbacks; + (void) memset(&callbacks, 0, sizeof (callbacks)); + callbacks.MethodExit = &cbMethodExit; + err = jvmti->SetEventCallbacks(&callbacks, (int) sizeof (jvmtiEventCallbacks)); + check_jvmti_error(err, "SetEventCallbacks"); + jvmti_env = jvmti; + return JNI_OK; +} + + +extern "C" { +JNIEXPORT void JNICALL +Java_TestPoppedByException_enable(JNIEnv *jni, jclass clazz) { + jthread thread = get_current_thread(jvmti_env, jni); + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, thread); +} + + +JNIEXPORT void JNICALL +Java_TestPoppedByException_disableAndCheck(JNIEnv *jni, jclass clazz) { + jthread thread = get_current_thread(jvmti_env, jni); + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_EXIT, thread); + if (!method_exit_posted) { + fatal(jni, "Failed to post method exit event."); + } + printf("The expected method_exit posted.\n"); +} + +} From bc08fd0dbc07c30a8dd3e53a8eb947ee233cbd7d Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 21 Aug 2025 08:43:31 -0700 Subject: [PATCH 19/26] bugid fixed: --- .../PendingException/TestMethodExitWithPendingException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java index 10bbd79fc52ae..30fec129f10cf 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java @@ -26,7 +26,7 @@ * @summary Test verifies that MethodExit event is correctly posted * if method is called while there is a pending exception on this thread. * - * @bug 8365192 + * @bug 8365937 * @run main/othervm/native -agentlib:TestMethodExitWithPendingException TestMethodExitWithPendingException */ public class TestMethodExitWithPendingException { From b37325eacb0d9354d85e05a5e40bf1ad3289f13e Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 21 Aug 2025 08:46:53 -0700 Subject: [PATCH 20/26] fixed ident --- src/hotspot/share/prims/jvmtiExport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index af8d15fae5b5a..aaa7ecf321cce 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1838,7 +1838,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur return; } - Handle result; + Handle result; jvalue value; value.j = 0L; From e8343e08de00ddc7b6aa55d886fef756c6a23b42 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 21 Aug 2025 08:51:54 -0700 Subject: [PATCH 21/26] more comments in the test --- .../PendingException/TestMethodExitWithPendingException.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java index 30fec129f10cf..bdf139511924b 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java @@ -39,7 +39,9 @@ static String exceptionExit() { } - // Called from ExceptionExit MethodExit callback via JNI + // Called from ExceptionExit MethodExit callback via JNI. + // So MyRuntimeException is thrown already and hasn't been caught yet + // when this method is called. static String upCall() { return "MyNewString"; } From d9319d90978899ccd2acb4a35eb2c3cc92150c57 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 21 Aug 2025 08:58:38 -0700 Subject: [PATCH 22/26] assertion added. --- src/hotspot/share/prims/jvmtiExport.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index aaa7ecf321cce..9b441481e3a25 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1847,6 +1847,8 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur // we just call into the interpreter to convert this into a jvalue. oop oop_result; BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); + assert(type == T_VOID || current_frame.interpreter_frame_expression_stack_size() > 0, + "Stack shouldn't be empty"); if (is_reference_type(type)) { result = Handle(thread, oop_result); value.l = JNIHandles::make_local(thread, result()); From c5415aa6b5dfae280784d067142e98326fa8638f Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 28 Aug 2025 09:33:50 -0700 Subject: [PATCH 23/26] comment fixed --- src/hotspot/share/prims/jvmtiExport.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 9b441481e3a25..9815cf572c94f 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1859,9 +1859,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur // depth 0 as it is already late in the method exiting dance. state->set_top_frame_is_exiting(); - // Deferred transition to VM, so we can stash away the return oop before GC - // Note that this transition is not needed when throwing an exception, because - // there is no oop to retain. + // Deferred transition to VM, so we can stash away the return oop before GC. JavaThread* current = thread; // for JRT_BLOCK JRT_BLOCK post_method_exit_inner(thread, mh, state, false, current_frame, value); From 4e05639a5fa7777e6c20e4d5aefa09c29b56616f Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 28 Aug 2025 09:37:56 -0700 Subject: [PATCH 24/26] Apply suggestions from code review Co-authored-by: David Holmes <62092539+dholmes-ora@users.noreply.github.com> --- src/hotspot/share/prims/jvmtiExport.cpp | 4 ++-- .../TestMethodExitWithPendingException.java | 2 +- .../libTestMethodExitWithPendingException.cpp | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 9815cf572c94f..1d6f7b1806c81 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1848,7 +1848,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur oop oop_result; BasicType type = current_frame.interpreter_frame_result(&oop_result, &value); assert(type == T_VOID || current_frame.interpreter_frame_expression_stack_size() > 0, - "Stack shouldn't be empty"); + "Stack shouldn't be empty"); if (is_reference_type(type)) { result = Handle(thread, oop_result); value.l = JNIHandles::make_local(thread, result()); @@ -1862,7 +1862,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur // Deferred transition to VM, so we can stash away the return oop before GC. JavaThread* current = thread; // for JRT_BLOCK JRT_BLOCK - post_method_exit_inner(thread, mh, state, false, current_frame, value); + post_method_exit_inner(thread, mh, state, false /* not exception exit */, current_frame, value); JRT_BLOCK_END // The JRT_BLOCK_END can safepoint in ThreadInVMfromJava desctructor. Now it is safe to allow diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java index bdf139511924b..ebeb157d633a9 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/TestMethodExitWithPendingException.java @@ -24,7 +24,7 @@ /* * @test * @summary Test verifies that MethodExit event is correctly posted - * if method is called while there is a pending exception on this thread. + * if method is called while there is a pending exception on this thread. * * @bug 8365937 * @run main/othervm/native -agentlib:TestMethodExitWithPendingException TestMethodExitWithPendingException diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp index 45eee52f7b815..83693a13a34d5 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp @@ -30,7 +30,7 @@ jvmtiEnv* jvmti_env; bool method_exit_posted = false; // This method exit callback actually works only for 2 methods: // 1) for ExceptionExit it verifies that method exit -// has been popped by exception and call 'upCall' mthod using JNI. +// has been popped by exception and calls 'upCall' method using JNI. // 2) for upCall method it verifies that event has correct // return value and was not popped by exception. // The event callback just exits for all other methods. @@ -64,7 +64,7 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, return; } jmethodID upcall_method = jni->GetStaticMethodID(main_class, - "upCall", "()Ljava/lang/String;"); + "upCall", "()Ljava/lang/String;"); if (upcall_method == nullptr) { fatal(jni,"Can't find upCall method."); } @@ -101,11 +101,12 @@ Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { err = jvmti->SetEventCallbacks(&callbacks, (int) sizeof (jvmtiEventCallbacks)); check_jvmti_error(err, "SetEventCallbacks"); jvmti_env = jvmti; - return JNI_OK; + return JNI_OK; } extern "C" { + JNIEXPORT void JNICALL Java_TestMethodExitWithPendingException_enable(JNIEnv *jni, jclass clazz) { jthread thread = get_current_thread(jvmti_env, jni); @@ -122,4 +123,4 @@ Java_TestMethodExitWithPendingException_disableAndCheck(JNIEnv *jni, jclass claz } } -} +} // extern "C" From bf2c90f2b3cc362af186ab00d57c9f8c2a3a6668 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Fri, 29 Aug 2025 12:07:05 -0700 Subject: [PATCH 25/26] Update test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp Co-authored-by: Patricio Chilano Mateo --- .../PendingException/libTestMethodExitWithPendingException.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp index 83693a13a34d5..12ee7a0536b31 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp @@ -45,7 +45,7 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, jstring upcall_result = (jstring) return_value.l; const char *str = jni->GetStringUTFChars(upcall_result, nullptr); if (str == nullptr) { - fatal(jni ,"Failed to convert Java string to C string."); + fatal(jni, "Failed to convert Java string to C string."); } if (strcmp("MyNewString", str) != 0) { fatal(jni, "The upCall result value is incorrect."); From 096079780fd5fb0ef3fdc1124fb7d49d5a9b45af Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Fri, 29 Aug 2025 12:07:56 -0700 Subject: [PATCH 26/26] Apply suggestions from code review Co-authored-by: Patricio Chilano Mateo --- .../libTestMethodExitWithPendingException.cpp | 6 +++--- .../PoppedByException/libTestPoppedByException.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp index 12ee7a0536b31..55a6cf059a60f 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PendingException/libTestMethodExitWithPendingException.cpp @@ -60,20 +60,20 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, } jclass main_class = jni->FindClass("TestMethodExitWithPendingException"); if (main_class == nullptr) { - fatal(jni,"Can't find TestMethodExitWithPendingException class."); + fatal(jni, "Can't find TestMethodExitWithPendingException class."); return; } jmethodID upcall_method = jni->GetStaticMethodID(main_class, "upCall", "()Ljava/lang/String;"); if (upcall_method == nullptr) { - fatal(jni,"Can't find upCall method."); + fatal(jni, "Can't find upCall method."); } // Call 'upCall' method while current thread has exception // that has been thrown but hasn't been caught yet. jstring upcall_result = (jstring) jni->CallStaticObjectMethod(main_class, upcall_method); const char *str = jni->GetStringUTFChars(upcall_result, nullptr); if (str == nullptr) { - fatal(jni ,"Failed to convert Java string to C string."); + fatal(jni, "Failed to convert Java string to C string."); return; } if (strcmp("MyNewString", str) != 0) { diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp index 864b871b58c62..645057e24ebc7 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/MethodExit/PoppedByException/libTestPoppedByException.cpp @@ -36,7 +36,7 @@ cbMethodExit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method, fatal(jni, "The method's was_popped_by_exception value is incorrect."); } if (return_value.l != nullptr) { - fatal(jni ,"return_value should be nullptr."); + fatal(jni, "return_value should be nullptr."); } method_exit_posted = true; }