| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef ART_OPENJDKJVMTI_EVENTS_INL_H_ |
| #define ART_OPENJDKJVMTI_EVENTS_INL_H_ |
| |
| #include <array> |
| #include <type_traits> |
| #include <tuple> |
| |
| #include "base/mutex-inl.h" |
| #include "events.h" |
| #include "jni/jni_internal.h" |
| #include "nativehelper/scoped_local_ref.h" |
| #include "runtime-inl.h" |
| #include "scoped_thread_state_change-inl.h" |
| #include "stack.h" |
| #include "ti_breakpoint.h" |
| #include "ti_thread.h" |
| |
| #include "art_jvmti.h" |
| |
| namespace openjdkjvmti { |
| |
| static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) { |
| if (UNLIKELY(e == JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) { |
| if (env->capabilities.can_retransform_classes) { |
| return ArtJvmtiEvent::kClassFileLoadHookRetransformable; |
| } else { |
| return ArtJvmtiEvent::kClassFileLoadHookNonRetransformable; |
| } |
| } else { |
| return static_cast<ArtJvmtiEvent>(e); |
| } |
| } |
| |
| namespace impl { |
| |
| // Helper for ensuring that the dispatch environment is suitably provisioned. 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: |
| ScopedEventDispatchEnvironment() : env_(nullptr), throw_(nullptr, nullptr) { |
| DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative); |
| } |
| |
| explicit ScopedEventDispatchEnvironment(JNIEnv* env) |
| : env_(env), |
| throw_(env_, env_->ExceptionOccurred()) { |
| DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::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(); |
| } |
| |
| ~ScopedEventDispatchEnvironment() { |
| if (env_ != nullptr) { |
| if (throw_.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(throw_.get()); |
| } |
| env_->PopLocalFrame(nullptr); |
| } |
| DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative); |
| } |
| |
| private: |
| JNIEnv* env_; |
| ScopedLocalRef<jthrowable> throw_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatchEnvironment); |
| }; |
| |
| // Infrastructure to achieve type safety for event dispatch. |
| |
| #define FORALL_EVENT_TYPES(fn) \ |
| fn(VMInit, ArtJvmtiEvent::kVmInit) \ |
| fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ |
| fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ |
| fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ |
| fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ |
| fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ |
| fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ |
| fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ |
| fn(VMStart, ArtJvmtiEvent::kVmStart) \ |
| fn(Exception, ArtJvmtiEvent::kException) \ |
| fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ |
| fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ |
| fn(FramePop, ArtJvmtiEvent::kFramePop) \ |
| fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ |
| fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ |
| fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ |
| fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ |
| fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ |
| fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ |
| fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ |
| fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ |
| fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ |
| fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ |
| fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ |
| fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ |
| fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ |
| fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ |
| fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ |
| fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ |
| fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ |
| fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ |
| fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ |
| fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ |
| fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) \ |
| fn(StructuralDexFileLoadHook, ArtJvmtiEvent::kStructuralDexFileLoadHook) |
| |
| template <ArtJvmtiEvent kEvent> |
| struct EventFnType { |
| }; |
| |
| #define EVENT_FN_TYPE(name, enum_name) \ |
| template <> \ |
| struct EventFnType<enum_name> { \ |
| using type = decltype(ArtJvmtiEventCallbacks().name); \ |
| }; |
| |
| FORALL_EVENT_TYPES(EVENT_FN_TYPE) |
| |
| #undef EVENT_FN_TYPE |
| |
| #define MAKE_EVENT_HANDLER_FUNC(name, enum_name) \ |
| template<> \ |
| struct EventHandlerFunc<enum_name> { \ |
| using EventFnType = typename impl::EventFnType<enum_name>::type; \ |
| explicit EventHandlerFunc(ArtJvmTiEnv* env) \ |
| : env_(env), \ |
| fn_(env_->event_callbacks == nullptr ? nullptr : env_->event_callbacks->name) { } \ |
| \ |
| template <typename ...Args> \ |
| ALWAYS_INLINE \ |
| void ExecuteCallback(JNIEnv* jnienv, Args... args) const { \ |
| if (fn_ != nullptr) { \ |
| ScopedEventDispatchEnvironment sede(jnienv); \ |
| DoExecute(jnienv, args...); \ |
| } \ |
| } \ |
| \ |
| template <typename ...Args> \ |
| ALWAYS_INLINE \ |
| void ExecuteCallback(Args... args) const { \ |
| if (fn_ != nullptr) { \ |
| ScopedEventDispatchEnvironment sede; \ |
| DoExecute(args...); \ |
| } \ |
| } \ |
| \ |
| private: \ |
| template <typename ...Args> \ |
| ALWAYS_INLINE \ |
| inline void DoExecute(Args... args) const { \ |
| static_assert(std::is_same<EventFnType, void(*)(jvmtiEnv*, Args...)>::value, \ |
| "Unexpected different type of ExecuteCallback"); \ |
| fn_(env_, args...); \ |
| } \ |
| \ |
| public: \ |
| ArtJvmTiEnv* env_; \ |
| EventFnType fn_; \ |
| }; |
| |
| FORALL_EVENT_TYPES(MAKE_EVENT_HANDLER_FUNC) |
| |
| #undef MAKE_EVENT_HANDLER_FUNC |
| |
| #undef FORALL_EVENT_TYPES |
| |
| } // namespace impl |
| |
| template <ArtJvmtiEvent kEvent, typename ...Args> |
| inline std::vector<impl::EventHandlerFunc<kEvent>> EventHandler::CollectEvents(art::Thread* thread, |
| Args... args) const { |
| art::ReaderMutexLock mu(thread, envs_lock_); |
| std::vector<impl::EventHandlerFunc<kEvent>> handlers; |
| for (ArtJvmTiEnv* env : envs) { |
| if (ShouldDispatch<kEvent>(env, thread, args...)) { |
| impl::EventHandlerFunc<kEvent> h(env); |
| handlers.push_back(h); |
| } |
| } |
| return handlers; |
| } |
| |
| // 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. |
| template <ArtJvmtiEvent kEvent> |
| inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, |
| JNIEnv* jnienv, |
| jclass class_being_redefined, |
| jobject loader, |
| const char* name, |
| jobject protection_domain, |
| jint class_data_len, |
| const unsigned char* class_data, |
| jint* new_class_data_len, |
| unsigned char** new_class_data) const { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || |
| kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable || |
| kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "Unsupported event"); |
| DCHECK(*new_class_data == nullptr); |
| jint current_len = class_data_len; |
| unsigned char* current_class_data = const_cast<unsigned char*>(class_data); |
| std::vector<impl::EventHandlerFunc<kEvent>> handlers = |
| CollectEvents<kEvent>(thread, |
| jnienv, |
| class_being_redefined, |
| loader, |
| name, |
| protection_domain, |
| class_data_len, |
| class_data, |
| new_class_data_len, |
| new_class_data); |
| ArtJvmTiEnv* last_env = nullptr; |
| for (const impl::EventHandlerFunc<kEvent>& event : handlers) { |
| jint new_len = 0; |
| unsigned char* new_data = nullptr; |
| ExecuteCallback<kEvent>(event, |
| 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 = event.env_; |
| current_class_data = new_data; |
| current_len = new_len; |
| } |
| } |
| if (last_env != nullptr) { |
| *new_class_data_len = current_len; |
| *new_class_data = current_class_data; |
| } |
| } |
| |
| // Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match |
| // exactly the argument types of the corresponding Jvmti kEvent function pointer. |
| |
| template <ArtJvmtiEvent kEvent, typename ...Args> |
| inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| 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()); |
| std::vector<impl::EventHandlerFunc<kEvent>> events = CollectEvents<kEvent>(thread, args...); |
| for (auto event : events) { |
| ExecuteCallback<kEvent>(event, args...); |
| } |
| } |
| |
| template <ArtJvmtiEvent kEvent, typename ...Args> |
| inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| std::vector<impl::EventHandlerFunc<kEvent>> events = CollectEvents<kEvent>(thread, |
| jnienv, |
| args...); |
| for (auto event : events) { |
| ExecuteCallback<kEvent>(event, jnienv, args...); |
| } |
| } |
| |
| template <ArtJvmtiEvent kEvent, typename ...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...)) { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| impl::EventHandlerFunc<kEvent> func(env); |
| ExecuteCallback<kEvent>(func, 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!"); |
| DCHECK(env != nullptr); |
| if (ShouldDispatch<kEvent, Args...>(env, thread, args...)) { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| impl::EventHandlerFunc<kEvent> func(env); |
| ExecuteCallback<kEvent>(func, args...); |
| } |
| } |
| |
| template <> |
| inline void EventHandler::DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>( |
| ArtJvmTiEnv* env, art::Thread* thread, jlong* obsolete_tag, jlong* new_tag) const { |
| static constexpr ArtJvmtiEvent kEvent = ArtJvmtiEvent::kObsoleteObjectCreated; |
| DCHECK(env != nullptr); |
| if (ShouldDispatch<kEvent>(env, thread, obsolete_tag, new_tag)) { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| impl::EventHandlerFunc<kEvent> func(env); |
| ExecuteCallback<kEvent>(func, obsolete_tag, new_tag); |
| } else { |
| // Unlike most others this has a default action to make sure that agents without knowledge of |
| // this extension get reasonable behavior. |
| jlong temp = *obsolete_tag; |
| *obsolete_tag = *new_tag; |
| *new_tag = temp; |
| } |
| } |
| |
| template <ArtJvmtiEvent kEvent, typename ...Args> |
| inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc<kEvent> handler, Args... args) { |
| handler.ExecuteCallback(args...); |
| } |
| |
| template <ArtJvmtiEvent kEvent, typename ...Args> |
| inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc<kEvent> handler, |
| JNIEnv* jnienv, |
| Args... args) { |
| handler.ExecuteCallback(jnienv, 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 bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kBreakpoint>( |
| ArtJvmTiEnv* env, |
| art::Thread* thread, |
| |
| [[maybe_unused]] JNIEnv* jnienv, |
| [[maybe_unused]] jthread jni_thread, |
| jmethodID jmethod, |
| jlocation location) const { |
| art::ReaderMutexLock lk(art::Thread::Current(), env->event_info_mutex_); |
| art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod); |
| return ShouldDispatchOnThread<ArtJvmtiEvent::kBreakpoint>(env, thread) && |
| env->breakpoints.find({method, location}) != env->breakpoints.end(); |
| } |
| |
| template <> |
| inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFramePop>( |
| ArtJvmTiEnv* env, |
| art::Thread* thread, |
| [[maybe_unused]] JNIEnv* jnienv, |
| [[maybe_unused]] jthread jni_thread, |
| [[maybe_unused]] jmethodID jmethod, |
| [[maybe_unused]] jboolean is_exception, |
| const art::ShadowFrame* frame) const { |
| // 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. |
| art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); |
| return env->notify_frames.erase(frame) != 0 && |
| !frame->GetSkipMethodExitEvents() && |
| ShouldDispatchOnThread<ArtJvmtiEvent::kFramePop>(env, thread); |
| } |
| |
| // Need to give custom specializations for FieldAccess and FieldModification since they need to |
| // filter out which particular fields agents want to get notified on. |
| // 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 bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldModification>( |
| ArtJvmTiEnv* env, |
| art::Thread* thread, |
| [[maybe_unused]] JNIEnv* jnienv, |
| [[maybe_unused]] jthread jni_thread, |
| [[maybe_unused]] jmethodID method, |
| [[maybe_unused]] jlocation location, |
| [[maybe_unused]] jclass field_klass, |
| [[maybe_unused]] jobject object, |
| jfieldID field, |
| [[maybe_unused]] char type_char, |
| [[maybe_unused]] jvalue val) const { |
| art::ReaderMutexLock lk(art::Thread::Current(), env->event_info_mutex_); |
| return ShouldDispatchOnThread<ArtJvmtiEvent::kFieldModification>(env, thread) && |
| env->modify_watched_fields.find( |
| art::jni::DecodeArtField(field)) != env->modify_watched_fields.end(); |
| } |
| |
| template <> |
| inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldAccess>( |
| ArtJvmTiEnv* env, |
| art::Thread* thread, |
| [[maybe_unused]] JNIEnv* jnienv, |
| [[maybe_unused]] jthread jni_thread, |
| [[maybe_unused]] jmethodID method, |
| [[maybe_unused]] jlocation location, |
| [[maybe_unused]] jclass field_klass, |
| [[maybe_unused]] jobject object, |
| jfieldID field) const { |
| art::ReaderMutexLock lk(art::Thread::Current(), env->event_info_mutex_); |
| 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>( |
| impl::EventHandlerFunc<ArtJvmtiEvent::kFramePop> event, |
| JNIEnv* jnienv, |
| jthread jni_thread, |
| jmethodID jmethod, |
| jboolean is_exception, |
| [[maybe_unused]] const art::ShadowFrame* frame) { |
| ExecuteCallback<ArtJvmtiEvent::kFramePop>(event, jnienv, jni_thread, jmethod, is_exception); |
| } |
| |
| struct ScopedDisablePopFrame { |
| public: |
| explicit ScopedDisablePopFrame(art::Thread* thread) : thread_(thread) { |
| art::Locks::mutator_lock_->AssertSharedHeld(thread_); |
| art::MutexLock mu(thread_, *art::Locks::thread_list_lock_); |
| JvmtiGlobalTLSData* data = ThreadUtil::GetOrCreateGlobalTLSData(thread_); |
| current_top_frame_ = art::StackVisitor::ComputeNumFrames( |
| thread_, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames); |
| old_disable_frame_pop_depth_ = data->disable_pop_frame_depth; |
| data->disable_pop_frame_depth = current_top_frame_; |
| // Check that we cleaned up any old disables. This should only increase (or be equals if we do |
| // another ClassLoad/Prepare recursively). |
| DCHECK(old_disable_frame_pop_depth_ == JvmtiGlobalTLSData::kNoDisallowedPopFrame || |
| current_top_frame_ >= old_disable_frame_pop_depth_) |
| << "old: " << old_disable_frame_pop_depth_ << " current: " << current_top_frame_; |
| } |
| |
| ~ScopedDisablePopFrame() { |
| art::Locks::mutator_lock_->AssertSharedHeld(thread_); |
| art::MutexLock mu(thread_, *art::Locks::thread_list_lock_); |
| JvmtiGlobalTLSData* data = ThreadUtil::GetGlobalTLSData(thread_); |
| DCHECK_EQ(data->disable_pop_frame_depth, current_top_frame_); |
| data->disable_pop_frame_depth = old_disable_frame_pop_depth_; |
| } |
| |
| private: |
| art::Thread* thread_; |
| size_t current_top_frame_; |
| size_t old_disable_frame_pop_depth_; |
| }; |
| // We want to prevent the use of PopFrame when reporting either of these events. |
| template <ArtJvmtiEvent kEvent> |
| inline void EventHandler::DispatchClassLoadOrPrepareEvent(art::Thread* thread, |
| JNIEnv* jnienv, |
| jthread jni_thread, |
| jclass klass) const { |
| ScopedDisablePopFrame sdpf(thread); |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| std::vector<impl::EventHandlerFunc<kEvent>> events = CollectEvents<kEvent>(thread, |
| jnienv, |
| jni_thread, |
| klass); |
| |
| for (auto event : events) { |
| ExecuteCallback<kEvent>(event, jnienv, jni_thread, klass); |
| } |
| } |
| |
| template <> |
| inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassLoad>(art::Thread* thread, |
| JNIEnv* jnienv, |
| jthread jni_thread, |
| jclass klass) const { |
| DispatchClassLoadOrPrepareEvent<ArtJvmtiEvent::kClassLoad>(thread, jnienv, jni_thread, klass); |
| } |
| template <> |
| inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassPrepare>(art::Thread* thread, |
| JNIEnv* jnienv, |
| jthread jni_thread, |
| jclass klass) const { |
| DispatchClassLoadOrPrepareEvent<ArtJvmtiEvent::kClassPrepare>(thread, jnienv, jni_thread, klass); |
| } |
| |
| // Need to give a custom specialization for NativeMethodBind since it has to deal with an out |
| // variable. |
| template <> |
| inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::Thread* thread, |
| JNIEnv* jnienv, |
| jthread jni_thread, |
| jmethodID method, |
| void* cur_method, |
| void** new_method) const { |
| art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); |
| std::vector<impl::EventHandlerFunc<ArtJvmtiEvent::kNativeMethodBind>> events = |
| CollectEvents<ArtJvmtiEvent::kNativeMethodBind>(thread, |
| jnienv, |
| jni_thread, |
| method, |
| cur_method, |
| new_method); |
| *new_method = cur_method; |
| for (auto event : events) { |
| *new_method = cur_method; |
| ExecuteCallback<ArtJvmtiEvent::kNativeMethodBind>(event, |
| 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 |
| // ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper. |
| // The following two DispatchEvent specializations dispatch to it. |
| template <> |
| inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( |
| art::Thread* thread, |
| JNIEnv* jnienv, |
| jclass class_being_redefined, |
| jobject loader, |
| const char* name, |
| jobject protection_domain, |
| jint class_data_len, |
| const unsigned char* class_data, |
| jint* new_class_data_len, |
| unsigned char** new_class_data) const { |
| return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( |
| thread, |
| jnienv, |
| class_being_redefined, |
| loader, |
| name, |
| protection_domain, |
| class_data_len, |
| class_data, |
| new_class_data_len, |
| new_class_data); |
| } |
| |
| template <> |
| inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( |
| art::Thread* thread, |
| JNIEnv* jnienv, |
| jclass class_being_redefined, |
| jobject loader, |
| const char* name, |
| jobject protection_domain, |
| jint class_data_len, |
| const unsigned char* class_data, |
| jint* new_class_data_len, |
| unsigned char** new_class_data) const { |
| return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( |
| thread, |
| jnienv, |
| class_being_redefined, |
| loader, |
| name, |
| protection_domain, |
| class_data_len, |
| class_data, |
| new_class_data_len, |
| new_class_data); |
| } |
| |
| template <> |
| inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>( |
| art::Thread* thread, |
| JNIEnv* jnienv, |
| jclass class_being_redefined, |
| jobject loader, |
| const char* name, |
| jobject protection_domain, |
| jint class_data_len, |
| const unsigned char* class_data, |
| jint* new_class_data_len, |
| unsigned char** new_class_data) const { |
| return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kStructuralDexFileLoadHook>( |
| thread, |
| jnienv, |
| class_being_redefined, |
| loader, |
| name, |
| protection_domain, |
| class_data_len, |
| class_data, |
| new_class_data_len, |
| new_class_data); |
| } |
| |
| template <ArtJvmtiEvent kEvent> |
| inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const { |
| bool dispatch = env->event_masks.global_event_mask.Test(kEvent); |
| |
| if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) { |
| EventMask* mask = env->event_masks.GetEventMaskOrNull(thread); |
| dispatch = mask != nullptr && mask->Test(kEvent); |
| } |
| return dispatch; |
| } |
| |
| template <ArtJvmtiEvent kEvent, typename... Args> |
| inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, |
| art::Thread* thread, |
| [[maybe_unused]] Args... args) 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) { |
| art::WriterMutexLock mu(art::Thread::Current(), envs_lock_); |
| RecalculateGlobalEventMaskLocked(event); |
| } |
| |
| inline void EventHandler::RecalculateGlobalEventMaskLocked(ArtJvmtiEvent event) { |
| bool union_value = false; |
| for (const ArtJvmTiEnv* stored_env : envs) { |
| if (stored_env == nullptr) { |
| continue; |
| } |
| union_value |= stored_env->event_masks.global_event_mask.Test(event); |
| union_value |= stored_env->event_masks.unioned_thread_event_mask.Test(event); |
| if (union_value) { |
| break; |
| } |
| } |
| global_mask.Set(event, union_value); |
| } |
| |
| inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, |
| const jvmtiCapabilities& caps, |
| bool added) { |
| ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable |
| : ArtJvmtiEvent::kClassFileLoadHookRetransformable; |
| return (added && caps.can_access_local_variables == 1) || |
| caps.can_generate_breakpoint_events == 1 || |
| caps.can_pop_frame == 1 || |
| caps.can_force_early_return == 1 || |
| (caps.can_retransform_classes == 1 && |
| IsEventEnabledAnywhere(event) && |
| env->event_masks.IsEnabledAnywhere(event)); |
| } |
| |
| inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, |
| const jvmtiCapabilities& caps, |
| bool added) { |
| if (UNLIKELY(NeedsEventUpdate(env, caps, added))) { |
| env->event_masks.HandleChangedCapabilities(caps, added); |
| if (caps.can_retransform_classes == 1) { |
| RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable); |
| RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); |
| } |
| if (added && caps.can_access_local_variables == 1) { |
| HandleLocalAccessCapabilityAdded(); |
| } |
| if (caps.can_generate_breakpoint_events == 1) { |
| HandleBreakpointEventsChanged(added); |
| } |
| if ((caps.can_pop_frame == 1 || caps.can_force_early_return == 1) && added) { |
| // TODO We should keep track of how many of these have been enabled and remove it if there are |
| // no more possible users. This isn't expected to be too common. |
| art::Runtime::Current()->SetNonStandardExitsEnabled(); |
| } |
| } |
| } |
| |
| } // namespace openjdkjvmti |
| |
| #endif // ART_OPENJDKJVMTI_EVENTS_INL_H_ |