diff options
author | 2017-09-12 08:57:31 -0700 | |
---|---|---|
committer | 2017-09-18 14:18:07 -0700 | |
commit | 9df79b72d1f5fb7fd731761c744eb119d02b45ee (patch) | |
tree | b70c735d5ca26b1135c18770442e9840b3cdf19e | |
parent | 41006c6e8c0c5132a22bb7e100b6cd545dbb55a6 (diff) |
Cleanup and consolidate JVMTI event code.
Various cleanups around JVMTI event code.
Ensure that we always store and restore exceptions.
Ensure we always give agents a local frame.
Ensure that we have static_asserts to verify that we are calling
events with appropriate types.
Various other improvements.
Test: ./test.py --host -j50
Change-Id: I71937d1575efca5096c9d5218203dc8201e3bb79
-rw-r--r-- | openjdkjvmti/events-inl.h | 339 | ||||
-rw-r--r-- | openjdkjvmti/events.cc | 90 | ||||
-rw-r--r-- | openjdkjvmti/events.h | 34 | ||||
-rw-r--r-- | openjdkjvmti/object_tagging.cc | 2 | ||||
-rw-r--r-- | openjdkjvmti/ti_class.cc | 14 | ||||
-rw-r--r-- | test/912-classes/classes.cc | 90 |
6 files changed, 335 insertions, 234 deletions
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 31edc73ac6..ab8e6def2d 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -18,10 +18,13 @@ #define ART_OPENJDKJVMTI_EVENTS_INL_H_ #include <array> +#include <type_traits> +#include <tuple> #include "events.h" #include "jni_internal.h" #include "nativehelper/ScopedLocalRef.h" +#include "scoped_thread_state_change-inl.h" #include "ti_breakpoint.h" #include "art_jvmti.h" @@ -115,7 +118,6 @@ FORALL_EVENT_TYPES(GET_CALLBACK) // C++ does not allow partial template function specialization. The dispatch for our separated // ClassFileLoadHook event types is the same, so use this helper for code deduplication. -// TODO Locking of some type! template <ArtJvmtiEvent kEvent> inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, JNIEnv* jnienv, @@ -137,37 +139,30 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, if (env == nullptr) { continue; } - if (ShouldDispatch<kEvent>(env, thread)) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - jint new_len = 0; - unsigned char* new_data = nullptr; - auto callback = impl::GetCallback<kEvent>(env); - callback(env, - jnienv, - class_being_redefined, - loader, - name, - protection_domain, - current_len, - current_class_data, - &new_len, - &new_data); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - if (new_data != nullptr && new_data != current_class_data) { - // Destroy the data the last transformer made. We skip this if the previous state was the - // initial one since we don't know here which jvmtiEnv allocated it. - // NB Currently this doesn't matter since all allocations just go to malloc but in the - // future we might have jvmtiEnv's keep track of their allocations for leak-checking. - if (last_env != nullptr) { - last_env->Deallocate(current_class_data); - } - last_env = env; - current_class_data = new_data; - current_len = new_len; + jint new_len = 0; + unsigned char* new_data = nullptr; + DispatchEventOnEnv<kEvent>(env, + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + current_len, + static_cast<const unsigned char*>(current_class_data), + &new_len, + &new_data); + if (new_data != nullptr && new_data != current_class_data) { + // Destroy the data the last transformer made. We skip this if the previous state was the + // initial one since we don't know here which jvmtiEnv allocated it. + // NB Currently this doesn't matter since all allocations just go to malloc but in the + // future we might have jvmtiEnv's keep track of their allocations for leak-checking. + if (last_env != nullptr) { + last_env->Deallocate(current_class_data); } + last_env = env; + current_class_data = new_data; + current_len = new_len; } } if (last_env != nullptr) { @@ -180,97 +175,125 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, // exactly the argument types of the corresponding Jvmti kEvent function pointer. template <ArtJvmtiEvent kEvent, typename ...Args> +inline void EventHandler::ExecuteCallback(ArtJvmTiEnv* env, Args... args) { + using FnType = typename impl::EventFnType<kEvent>::type; + FnType callback = impl::GetCallback<kEvent>(env); + if (callback != nullptr) { + (*callback)(env, args...); + } +} + +template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const { + static_assert(!std::is_same<JNIEnv*, + typename std::decay_t< + std::tuple_element_t<0, std::tuple<Args..., nullptr_t>>>>::value, + "Should be calling DispatchEvent with explicit JNIEnv* argument!"); + DCHECK(thread == nullptr || !thread->IsExceptionPending()); for (ArtJvmTiEnv* env : envs) { if (env != nullptr) { - DispatchEvent<kEvent, Args...>(env, thread, args...); + DispatchEventOnEnv<kEvent, Args...>(env, thread, args...); } } } -// Events with JNIEnvs need to stash pending exceptions since they can cause new ones to be thrown. -// In accordance with the JVMTI specification we allow exceptions originating from events to -// overwrite the current exception, including exceptions originating from earlier events. -// TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list of the -// newest exception. +// Helper for ensuring that the dispatch environment is sane. Events with JNIEnvs need to stash +// pending exceptions since they can cause new ones to be thrown. In accordance with the JVMTI +// specification we allow exceptions originating from events to overwrite the current exception, +// including exceptions originating from earlier events. +class ScopedEventDispatchEnvironment FINAL : public art::ValueObject { + public: + explicit ScopedEventDispatchEnvironment(JNIEnv* env) + : env_(env), + thr_(env_, env_->ExceptionOccurred()), + suspend_(art::Thread::Current(), art::kNative) { + // The spec doesn't say how much local data should be there, so we just give 128 which seems + // likely to be enough for most cases. + env_->PushLocalFrame(128); + env_->ExceptionClear(); + UNUSED(suspend_); + } + + ~ScopedEventDispatchEnvironment() { + if (thr_.get() != nullptr && !env_->ExceptionCheck()) { + // TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list + // of the newest exception. + env_->Throw(thr_.get()); + } + env_->PopLocalFrame(nullptr); + } + + private: + JNIEnv* env_; + ScopedLocalRef<jthrowable> thr_; + // Not actually unused. The destructor/constructor does important work. + art::ScopedThreadStateChange suspend_; + + DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatchEnvironment); +}; + template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const { for (ArtJvmTiEnv* env : envs) { if (env != nullptr) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - DispatchEvent<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } + DispatchEventOnEnv<kEvent, Args...>(env, thread, jnienv, args...); } } } template <ArtJvmtiEvent kEvent, typename ...Args> -inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const { - using FnType = void(jvmtiEnv*, Args...); - if (ShouldDispatch<kEvent>(env, thread)) { - FnType* callback = impl::GetCallback<kEvent>(env); - if (callback != nullptr) { - (*callback)(env, args...); - } +inline void EventHandler::DispatchEventOnEnv( + ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const { + DCHECK(env != nullptr); + if (ShouldDispatch<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...)) { + ScopedEventDispatchEnvironment sede(jnienv); + ExecuteCallback<kEvent, JNIEnv*, Args...>(env, jnienv, args...); } } +template <ArtJvmtiEvent kEvent, typename ...Args> +inline void EventHandler::DispatchEventOnEnv( + ArtJvmTiEnv* env, art::Thread* thread, Args... args) const { + static_assert(!std::is_same<JNIEnv*, + typename std::decay_t< + std::tuple_element_t<0, std::tuple<Args..., nullptr_t>>>>::value, + "Should be calling DispatchEventOnEnv with explicit JNIEnv* argument!"); + if (ShouldDispatch<kEvent>(env, thread, args...)) { + ExecuteCallback<kEvent, Args...>(env, args...); + } +} + +// Events that need custom logic for if we send the event but are otherwise normal. This includes +// the kBreakpoint, kFramePop, kFieldAccess, and kFieldModification events. + // Need to give custom specializations for Breakpoint since it needs to filter out which particular // methods/dex_pcs agents get notified on. template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID jmethod, - jlocation location) const { +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kBreakpoint>( + ArtJvmTiEnv* env, + art::Thread* thread, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID jmethod, + jlocation location) const { art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod); - for (ArtJvmTiEnv* env : envs) { - // Search for a breakpoint on this particular method and location. - if (env != nullptr && - ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) && - env->breakpoints.find({method, location}) != env->breakpoints.end()) { - // We temporarily clear any pending exceptions so the event can call back into java code. - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env); - (*callback)(env, jnienv, jni_thread, jmethod, location); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } + return ShouldDispatchOnThread<ArtJvmtiEvent::kBreakpoint>(env, thread) && + env->breakpoints.find({method, location}) != env->breakpoints.end(); } -// Need to give custom specializations for FramePop since it needs to filter out which particular -// agents get the event. This specialization gets an extra argument so we can determine which (if -// any) environments have the frame pop. template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>( +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFramePop>( + ArtJvmTiEnv* env, art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID jmethod, - jboolean is_exception, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID jmethod ATTRIBUTE_UNUSED, + jboolean is_exception ATTRIBUTE_UNUSED, const art::ShadowFrame* frame) const { - for (ArtJvmTiEnv* env : envs) { - // Search for the frame. Do this before checking if we need to send the event so that we don't - // have to deal with use-after-free or the frames being reallocated later. - if (env != nullptr && env->notify_frames.erase(frame) != 0) { - if (ShouldDispatch<ArtJvmtiEvent::kFramePop>(env, thread)) { - // We temporarily clear any pending exceptions so the event can call back into java code. - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kFramePop>(env); - (*callback)(env, jnienv, jni_thread, jmethod, is_exception); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } - } + // Search for the frame. Do this before checking if we need to send the event so that we don't + // have to deal with use-after-free or the frames being reallocated later. + return env->notify_frames.erase(frame) != 0 && + ShouldDispatchOnThread<ArtJvmtiEvent::kFramePop>(env, thread); } // Need to give custom specializations for FieldAccess and FieldModification since they need to @@ -278,64 +301,54 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>( // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This // could make the system more performant. template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID method, - jlocation location, - jclass field_klass, - jobject object, - jfieldID field, - char type_char, - jvalue val) const { - for (ArtJvmTiEnv* env : envs) { - if (env != nullptr && - ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) && - env->modify_watched_fields.find( - art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env); - (*callback)(env, - jnienv, - jni_thread, - method, - location, - field_klass, - object, - field, - type_char, - val); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldModification>( + ArtJvmTiEnv* env, + art::Thread* thread, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID method ATTRIBUTE_UNUSED, + jlocation location ATTRIBUTE_UNUSED, + jclass field_klass ATTRIBUTE_UNUSED, + jobject object ATTRIBUTE_UNUSED, + jfieldID field, + char type_char ATTRIBUTE_UNUSED, + jvalue val ATTRIBUTE_UNUSED) const { + return ShouldDispatchOnThread<ArtJvmtiEvent::kFieldModification>(env, thread) && + env->modify_watched_fields.find( + art::jni::DecodeArtField(field)) != env->modify_watched_fields.end(); } template <> -inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread, - JNIEnv* jnienv, - jthread jni_thread, - jmethodID method, - jlocation location, - jclass field_klass, - jobject object, - jfieldID field) const { - for (ArtJvmTiEnv* env : envs) { - if (env != nullptr && - ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) && - env->access_watched_fields.find( - art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) { - ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); - jnienv->ExceptionClear(); - auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env); - (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field); - if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { - jnienv->Throw(thr.get()); - } - } - } +inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldAccess>( + ArtJvmTiEnv* env, + art::Thread* thread, + JNIEnv* jnienv ATTRIBUTE_UNUSED, + jthread jni_thread ATTRIBUTE_UNUSED, + jmethodID method ATTRIBUTE_UNUSED, + jlocation location ATTRIBUTE_UNUSED, + jclass field_klass ATTRIBUTE_UNUSED, + jobject object ATTRIBUTE_UNUSED, + jfieldID field) const { + return ShouldDispatchOnThread<ArtJvmtiEvent::kFieldAccess>(env, thread) && + env->access_watched_fields.find( + art::jni::DecodeArtField(field)) != env->access_watched_fields.end(); +} + +// Need to give custom specializations for FramePop since it needs to filter out which particular +// agents get the event. This specialization gets an extra argument so we can determine which (if +// any) environments have the frame pop. +// TODO It might be useful to use more template magic to have this only define ShouldDispatch or +// something. +template <> +inline void EventHandler::ExecuteCallback<ArtJvmtiEvent::kFramePop>( + ArtJvmTiEnv* env, + JNIEnv* jnienv, + jthread jni_thread, + jmethodID jmethod, + jboolean is_exception, + const art::ShadowFrame* frame ATTRIBUTE_UNUSED) { + ExecuteCallback<ArtJvmtiEvent::kFramePop>( + env, jnienv, jni_thread, jmethod, is_exception); } // Need to give a custom specialization for NativeMethodBind since it has to deal with an out @@ -349,14 +362,21 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::T void** new_method) const { *new_method = cur_method; for (ArtJvmTiEnv* env : envs) { - if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) { - auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env); - (*callback)(env, jnienv, jni_thread, method, cur_method, new_method); + if (env != nullptr) { + *new_method = cur_method; + DispatchEventOnEnv<ArtJvmtiEvent::kNativeMethodBind>(env, + thread, + jnienv, + jni_thread, + method, + cur_method, + new_method); if (*new_method != nullptr) { cur_method = *new_method; } } } + *new_method = cur_method; } // C++ does not allow partial template function specialization. The dispatch for our separated @@ -386,6 +406,7 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetrans new_class_data_len, new_class_data); } + template <> inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( art::Thread* thread, @@ -412,8 +433,7 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetr } template <ArtJvmtiEvent kEvent> -inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, - art::Thread* thread) { +inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) { bool dispatch = env->event_masks.global_event_mask.Test(kEvent); if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) { @@ -423,6 +443,17 @@ inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, return dispatch; } +template <ArtJvmtiEvent kEvent, typename ...Args> +inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, + art::Thread* thread, + Args... args ATTRIBUTE_UNUSED) const { + static_assert(std::is_same<typename impl::EventFnType<kEvent>::type, + void(*)(jvmtiEnv*, Args...)>::value, + "Unexpected different type of shouldDispatch"); + + return ShouldDispatchOnThread<kEvent>(env, thread); +} + inline void EventHandler::RecalculateGlobalEventMask(ArtJvmtiEvent event) { bool union_value = false; for (const ArtJvmTiEnv* stored_env : envs) { diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 5911a0373d..1cc82b7750 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -192,6 +192,25 @@ static bool IsThreadControllable(ArtJvmtiEvent event) { } } +template<typename Type> +static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj); +} + +template<ArtJvmtiEvent kEvent, typename ...Args> +static void RunEventCallback(EventHandler* handler, + art::Thread* self, + art::JNIEnvExt* jnienv, + Args... args) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer())); + handler->DispatchEvent<kEvent>(self, + static_cast<JNIEnv*>(jnienv), + thread_jni.get(), + args...); +} + class JvmtiAllocationListener : public art::gc::AllocationListener { public: explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {} @@ -211,26 +230,17 @@ class JvmtiAllocationListener : public art::gc::AllocationListener { // jclass object_klass, // jlong size art::JNIEnvExt* jni_env = self->GetJniEnv(); - - jthread thread_peer; - if (self->IsStillStarting()) { - thread_peer = nullptr; - } else { - thread_peer = jni_env->AddLocalReference<jthread>(self->GetPeer()); - } - - ScopedLocalRef<jthread> thread(jni_env, thread_peer); ScopedLocalRef<jobject> object( jni_env, jni_env->AddLocalReference<jobject>(*obj)); ScopedLocalRef<jclass> klass( jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass())); - handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self, - reinterpret_cast<JNIEnv*>(jni_env), - thread.get(), - object.get(), - klass.get(), - static_cast<jlong>(byte_count)); + RunEventCallback<ArtJvmtiEvent::kVmObjectAlloc>(handler_, + self, + jni_env, + object.get(), + klass.get(), + static_cast<jlong>(byte_count)); } } @@ -250,38 +260,6 @@ static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, } } -template<typename Type> -static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj); -} - -template<ArtJvmtiEvent kEvent, typename ...Args> -static void RunEventCallback(EventHandler* handler, - art::Thread* self, - art::JNIEnvExt* jnienv, - Args... args) - REQUIRES_SHARED(art::Locks::mutator_lock_) { - ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer())); - art::StackHandleScope<1> hs(self); - art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException())); - self->ClearException(); - // Just give the event a good sized JNI frame. 100 should be fine. - jnienv->PushFrame(100); - { - // Need to do trampoline! :( - art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); - handler->DispatchEvent<kEvent>(self, - static_cast<JNIEnv*>(jnienv), - thread_jni.get(), - args...); - } - jnienv->PopFrame(); - if (!self->IsExceptionPending() && !old_exception.IsNull()) { - self->SetException(old_exception.Get()); - } -} - class JvmtiMonitorListener : public art::MonitorCallback { public: explicit JvmtiMonitorListener(EventHandler* handler) : handler_(handler) {} @@ -649,17 +627,15 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat void WatchedFramePop(art::Thread* self, const art::ShadowFrame& frame) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { - if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFramePop)) { art::JNIEnvExt* jnienv = self->GetJniEnv(); - jboolean is_exception_pending = self->IsExceptionPending(); - RunEventCallback<ArtJvmtiEvent::kFramePop>( - event_handler_, - self, - jnienv, - art::jni::EncodeArtMethod(frame.GetMethod()), - is_exception_pending, - &frame); - } + jboolean is_exception_pending = self->IsExceptionPending(); + RunEventCallback<ArtJvmtiEvent::kFramePop>( + event_handler_, + self, + jnienv, + art::jni::EncodeArtMethod(frame.GetMethod()), + is_exception_pending, + &frame); } static void FindCatchMethodsFromThrow(art::Thread* self, diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index 0d36c46fa1..a062e1589e 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -161,20 +161,21 @@ class EventHandler { ArtJvmtiEvent event, jvmtiEventMode mode); - // Dispatch event to all registered environments. + // Dispatch event to all registered environments. Since this one doesn't have a JNIEnv* it doesn't + // matter if it has the mutator_lock. template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE inline void DispatchEvent(art::Thread* thread, Args... args) const; + // Dispatch event to all registered environments stashing exceptions as needed. This works since // JNIEnv* is always the second argument if it is passed to an event. Needed since C++ does not // allow partial template function specialization. + // + // We need both of these since we want to make sure to push a stack frame when it is possible for + // the event to allocate local references. template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE - void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const; - // Dispatch event to the given environment, only. - template <ArtJvmtiEvent kEvent, typename ...Args> - ALWAYS_INLINE - inline void DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const; + inline void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const; // Tell the event handler capabilities were added/lost so it can adjust the sent events.If // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false @@ -184,10 +185,29 @@ class EventHandler { const jvmtiCapabilities& caps, bool added); + // Dispatch event to the given environment, only. + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + inline void DispatchEventOnEnv( + ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const; + + // Dispatch event to the given environment, only. + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + inline void DispatchEventOnEnv(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const; + private: template <ArtJvmtiEvent kEvent> ALWAYS_INLINE - static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread); + static inline bool ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread); + + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + static inline void ExecuteCallback(ArtJvmTiEnv* env, Args... args); + + template <ArtJvmtiEvent kEvent, typename ...Args> + ALWAYS_INLINE + inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const; ALWAYS_INLINE inline bool NeedsEventUpdate(ArtJvmTiEnv* env, diff --git a/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc index dcdd3ede13..6ba7165577 100644 --- a/openjdkjvmti/object_tagging.cc +++ b/openjdkjvmti/object_tagging.cc @@ -61,7 +61,7 @@ bool ObjectTagTable::DoesHandleNullOnSweep() { return event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kObjectFree); } void ObjectTagTable::HandleNullSweep(jlong tag) { - event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag); + event_handler_->DispatchEventOnEnv<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag); } } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc index 954b5d1d03..daf4a8b7f2 100644 --- a/openjdkjvmti/ti_class.cc +++ b/openjdkjvmti/ti_class.cc @@ -318,14 +318,11 @@ struct ClassCallback : public art::ClassLoadCallback { ScopedLocalRef<jthread> thread_jni( thread->GetJniEnv(), peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer)); - { - art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); - event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( - thread, - static_cast<JNIEnv*>(thread->GetJniEnv()), - thread_jni.get(), - jklass.get()); - } + event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); if (klass->IsTemp()) { AddTempClass(thread, jklass.get()); } @@ -348,7 +345,6 @@ struct ClassCallback : public art::ClassLoadCallback { ScopedLocalRef<jthread> thread_jni( thread->GetJniEnv(), peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer)); - art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>( thread, static_cast<JNIEnv*>(thread->GetJniEnv()), diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index 869eacd82c..9727057668 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -16,6 +16,7 @@ #include <stdio.h> +#include <condition_variable> #include <mutex> #include <vector> @@ -378,6 +379,48 @@ extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPreparePrintEv ClassLoadPreparePrinter::ClassPrepareCallback); } +template<typename T> +static jthread RunEventThread(const std::string& name, + jvmtiEnv* jvmti, + JNIEnv* env, + void (*func)(jvmtiEnv*, JNIEnv*, T*), + T* data) { + // Create a Thread object. + std::string name_str = name; + name_str += ": JVMTI_THREAD-Test912"; + ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name_str.c_str())); + CHECK(thread_name.get() != nullptr); + + ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread")); + CHECK(thread_klass.get() != nullptr); + + ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get())); + CHECK(thread.get() != nullptr); + + jmethodID initID = env->GetMethodID(thread_klass.get(), "<init>", "(Ljava/lang/String;)V"); + CHECK(initID != nullptr); + + env->CallNonvirtualVoidMethod(thread.get(), thread_klass.get(), initID, thread_name.get()); + CHECK(!env->ExceptionCheck()); + + // Run agent thread. + CheckJvmtiError(jvmti, jvmti->RunAgentThread(thread.get(), + reinterpret_cast<jvmtiStartFunction>(func), + reinterpret_cast<void*>(data), + JVMTI_THREAD_NORM_PRIORITY)); + return thread.release(); +} + +static void JoinTread(JNIEnv* env, jthread thr) { + ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread")); + CHECK(thread_klass.get() != nullptr); + + jmethodID joinID = env->GetMethodID(thread_klass.get(), "join", "()V"); + CHECK(joinID != nullptr); + + env->CallVoidMethod(thr, joinID); +} + class ClassLoadPrepareEquality { public: static constexpr const char* kClassName = "Lart/Test912$ClassE;"; @@ -389,6 +432,21 @@ class ClassLoadPrepareEquality { static constexpr const char* kWeakInitSig = "(Ljava/lang/Object;)V"; static constexpr const char* kWeakGetSig = "()Ljava/lang/Object;"; + static void AgentThreadTest(jvmtiEnv* jvmti ATTRIBUTE_UNUSED, + JNIEnv* env, + jobject* obj_global) { + jobject target = *obj_global; + jobject target_local = env->NewLocalRef(target); + { + std::unique_lock<std::mutex> lk(mutex_); + started_ = true; + cond_started_.notify_all(); + cond_finished_.wait(lk, [] { return finished_; }); + CHECK(finished_); + } + CHECK(env->IsSameObject(target, target_local)); + } + static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread ATTRIBUTE_UNUSED, @@ -398,9 +456,13 @@ class ClassLoadPrepareEquality { found_ = true; stored_class_ = jni_env->NewGlobalRef(klass); weakly_stored_class_ = jni_env->NewWeakGlobalRef(klass); - // The following is bad and relies on implementation details. But otherwise a test would be - // a lot more complicated. - local_stored_class_ = jni_env->NewLocalRef(klass); + // Check that we update the local refs. + agent_thread_ = static_cast<jthread>(jni_env->NewGlobalRef(RunEventThread<jobject>( + "local-ref", jenv, jni_env, &AgentThreadTest, static_cast<jobject*>(&stored_class_)))); + { + std::unique_lock<std::mutex> lk(mutex_); + cond_started_.wait(lk, [] { return started_; }); + } // Store the value into a field in the heap. SetOrCompare(jni_env, klass, true); } @@ -415,9 +477,14 @@ class ClassLoadPrepareEquality { CHECK(stored_class_ != nullptr); CHECK(jni_env->IsSameObject(stored_class_, klass)); CHECK(jni_env->IsSameObject(weakly_stored_class_, klass)); - CHECK(jni_env->IsSameObject(local_stored_class_, klass)); + { + std::unique_lock<std::mutex> lk(mutex_); + finished_ = true; + cond_finished_.notify_all(); + } // Look up the value in a field in the heap. SetOrCompare(jni_env, klass, false); + JoinTread(jni_env, agent_thread_); compared_ = true; } } @@ -487,14 +554,25 @@ class ClassLoadPrepareEquality { private: static jobject stored_class_; static jweak weakly_stored_class_; - static jobject local_stored_class_; + static jthread agent_thread_; + static std::mutex mutex_; + static bool started_; + static std::condition_variable cond_finished_; + static bool finished_; + static std::condition_variable cond_started_; static bool found_; static bool compared_; }; + jclass ClassLoadPrepareEquality::storage_class_ = nullptr; jobject ClassLoadPrepareEquality::stored_class_ = nullptr; jweak ClassLoadPrepareEquality::weakly_stored_class_ = nullptr; -jobject ClassLoadPrepareEquality::local_stored_class_ = nullptr; +jthread ClassLoadPrepareEquality::agent_thread_ = nullptr; +std::mutex ClassLoadPrepareEquality::mutex_; +bool ClassLoadPrepareEquality::started_ = false; +std::condition_variable ClassLoadPrepareEquality::cond_started_; +bool ClassLoadPrepareEquality::finished_ = false; +std::condition_variable ClassLoadPrepareEquality::cond_finished_; bool ClassLoadPrepareEquality::found_ = false; bool ClassLoadPrepareEquality::compared_ = false; |