diff options
author | Alex Light <allight@google.com> | 2018-10-12 17:58:18 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-10-12 17:58:18 +0000 |
commit | d55f6ee508a9a3c81bb1911bb203a22845bfab86 (patch) | |
tree | 38a8793c9d1c182771f89075c1218ec9d1c2da75 | |
parent | 332c8e36356b21d65c4003612334a681150a5643 (diff) | |
parent | 0aa7a5a6a7bc350b79351f52e26c97747e927acf (diff) | |
download | art-oreo-mr1-1.2-iot-release.tar.gz |
Merge "Revert^4 "JVMTI PopFrame support""android-o-mr1-iot-release-smart-display-r3oreo-mr1-1.2-iot-release
69 files changed, 5032 insertions, 100 deletions
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index 3213bbe91a..48f326a54d 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -313,10 +313,10 @@ class JvmtiFunctions { return StackUtil::GetFrameCount(env, thread, count_ptr); } - static jvmtiError PopFrame(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) { + static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_pop_frame); - return ERR(NOT_IMPLEMENTED); + return StackUtil::PopFrame(env, thread); } static jvmtiError GetFrameLocation(jvmtiEnv* env, diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index 82f3866c65..1218e3b9a7 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -249,7 +249,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_get_owned_monitor_info = 1, .can_get_current_contended_monitor = 1, .can_get_monitor_info = 1, - .can_pop_frame = 0, + .can_pop_frame = 1, .can_redefine_classes = 1, .can_signal_thread = 1, .can_get_source_file_name = 1, @@ -291,6 +291,7 @@ const jvmtiCapabilities kPotentialCapabilities = { // can_retransform_classes: // can_redefine_any_class: // can_redefine_classes: +// can_pop_frame: // We need to ensure that inlined code is either not present or can always be deoptimized. This // is not guaranteed for non-debuggable processes since we might have inlined bootclasspath code // on a threads stack. @@ -303,7 +304,7 @@ const jvmtiCapabilities kNonDebuggableUnsupportedCapabilities = { .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, .can_get_monitor_info = 0, - .can_pop_frame = 0, + .can_pop_frame = 1, .can_redefine_classes = 1, .can_signal_thread = 0, .can_get_source_file_name = 0, diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index e98517fdff..ca66556bb0 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -26,7 +26,9 @@ #include "jni/jni_internal.h" #include "nativehelper/scoped_local_ref.h" #include "scoped_thread_state_change-inl.h" +#include "stack.h" #include "ti_breakpoint.h" +#include "ti_thread.h" #include "art_jvmti.h" @@ -359,6 +361,7 @@ inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFramePop>( // 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->GetForcePopFrame() && ShouldDispatchOnThread<ArtJvmtiEvent::kFramePop>(env, thread); } @@ -418,6 +421,67 @@ inline void EventHandler::ExecuteCallback<ArtJvmtiEvent::kFramePop>( 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_; + 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 <> @@ -553,6 +617,7 @@ inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, : ArtJvmtiEvent::kClassFileLoadHookRetransformable; return (added && caps.can_access_local_variables == 1) || caps.can_generate_breakpoint_events == 1 || + caps.can_pop_frame == 1 || (caps.can_retransform_classes == 1 && IsEventEnabledAnywhere(event) && env->event_masks.IsEnabledAnywhere(event)); @@ -573,6 +638,11 @@ inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, if (caps.can_generate_breakpoint_events == 1) { HandleBreakpointEventsChanged(added); } + if (caps.can_pop_frame == 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(); + } } } diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index bf12cb191e..9f91a08b8b 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -301,6 +301,13 @@ class EventHandler { unsigned char** new_class_data) const REQUIRES(!envs_lock_); + template <ArtJvmtiEvent kEvent> + ALWAYS_INLINE inline void DispatchClassLoadOrPrepareEvent(art::Thread* thread, + JNIEnv* jnienv, + jthread jni_thread, + jclass klass) const + REQUIRES(!envs_lock_); + void HandleEventType(ArtJvmtiEvent event, bool enable); void HandleLocalAccessCapabilityAdded(); void HandleBreakpointEventsChanged(bool enable); diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc index 220ad22be5..5a98755c67 100644 --- a/openjdkjvmti/ti_stack.cc +++ b/openjdkjvmti/ti_stack.cc @@ -112,6 +112,23 @@ struct GetStackTraceVisitor : public art::StackVisitor { size_t stop; }; +art::ShadowFrame* FindFrameAtDepthVisitor::GetOrCreateShadowFrame(bool* created_frame) { + art::ShadowFrame* cur = GetCurrentShadowFrame(); + if (cur == nullptr) { + *created_frame = true; + art::ArtMethod* method = GetMethod(); + const uint16_t num_regs = method->DexInstructionData().RegistersSize(); + cur = GetThread()->FindOrCreateDebuggerShadowFrame(GetFrameId(), + num_regs, + method, + GetDexPc()); + DCHECK(cur != nullptr); + } else { + *created_frame = false; + } + return cur; +} + template <typename FrameFn> GetStackTraceVisitor<FrameFn> MakeStackTraceVisitor(art::Thread* thread_in, size_t start, @@ -1065,16 +1082,7 @@ jvmtiError StackUtil::NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) // From here we are sure to succeed. bool needs_instrument = false; // Get/create a shadow frame - art::ShadowFrame* shadow_frame = visitor.GetCurrentShadowFrame(); - if (shadow_frame == nullptr) { - needs_instrument = true; - const size_t frame_id = visitor.GetFrameId(); - const uint16_t num_regs = method->DexInstructionData().RegistersSize(); - shadow_frame = target->FindOrCreateDebuggerShadowFrame(frame_id, - num_regs, - method, - visitor.GetDexPc()); - } + art::ShadowFrame* shadow_frame = visitor.GetOrCreateShadowFrame(&needs_instrument); { art::WriterMutexLock lk(self, tienv->event_info_mutex_); // Mark shadow frame as needs_notify_pop_ @@ -1089,4 +1097,88 @@ jvmtiError StackUtil::NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) } while (true); } +jvmtiError StackUtil::PopFrame(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) { + art::Thread* self = art::Thread::Current(); + art::Thread* target; + do { + ThreadUtil::SuspendCheck(self); + art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_); + // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by a + // user-code suspension. We retry and do another SuspendCheck to clear this. + if (ThreadUtil::WouldSuspendForUserCodeLocked(self)) { + continue; + } + // From now on we know we cannot get suspended by user-code. + // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't + // have the 'suspend_lock' locked here. + art::ScopedObjectAccess soa(self); + art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_); + jvmtiError err = ERR(INTERNAL); + if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) { + return err; + } + { + art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_); + if (target == self || target->GetUserCodeSuspendCount() == 0) { + // We cannot be the current thread for this function. + return ERR(THREAD_NOT_SUSPENDED); + } + } + JvmtiGlobalTLSData* tls_data = ThreadUtil::GetGlobalTLSData(target); + constexpr art::StackVisitor::StackWalkKind kWalkKind = + art::StackVisitor::StackWalkKind::kIncludeInlinedFrames; + if (tls_data != nullptr && + tls_data->disable_pop_frame_depth != JvmtiGlobalTLSData::kNoDisallowedPopFrame && + tls_data->disable_pop_frame_depth == art::StackVisitor::ComputeNumFrames(target, + kWalkKind)) { + LOG(WARNING) << "Disallowing frame pop due to in-progress class-load/prepare. Frame at depth " + << tls_data->disable_pop_frame_depth << " was marked as un-poppable by the " + << "jvmti plugin. See b/117615146 for more information."; + return ERR(OPAQUE_FRAME); + } + // We hold the user_code_suspension_lock_ so the target thread is staying suspended until we are + // done. + std::unique_ptr<art::Context> context(art::Context::Create()); + FindFrameAtDepthVisitor final_frame(target, context.get(), 0); + FindFrameAtDepthVisitor penultimate_frame(target, context.get(), 1); + final_frame.WalkStack(); + penultimate_frame.WalkStack(); + + if (!final_frame.FoundFrame() || !penultimate_frame.FoundFrame()) { + // Cannot do it if there is only one frame! + return ERR(NO_MORE_FRAMES); + } + + art::ArtMethod* called_method = final_frame.GetMethod(); + art::ArtMethod* calling_method = penultimate_frame.GetMethod(); + if (calling_method->IsNative() || called_method->IsNative()) { + return ERR(OPAQUE_FRAME); + } + // From here we are sure to succeed. + + // Get/create a shadow frame + bool created_final_frame = false; + bool created_penultimate_frame = false; + art::ShadowFrame* called_shadow_frame = + final_frame.GetOrCreateShadowFrame(&created_final_frame); + art::ShadowFrame* calling_shadow_frame = + penultimate_frame.GetOrCreateShadowFrame(&created_penultimate_frame); + + CHECK_NE(called_shadow_frame, calling_shadow_frame) + << "Frames at different depths not different!"; + + // Tell the shadow-frame to return immediately and skip all exit events. + called_shadow_frame->SetForcePopFrame(true); + calling_shadow_frame->SetForceRetryInstruction(true); + + // Make sure can we will go to the interpreter and use the shadow frames. The early return for + // the final frame will force everything to the interpreter so we only need to instrument if it + // was not present. + if (created_final_frame) { + DeoptManager::Get()->DeoptimizeThread(target); + } + return OK; + } while (true); +} + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h index b41fa4bf5a..55c4269086 100644 --- a/openjdkjvmti/ti_stack.h +++ b/openjdkjvmti/ti_stack.h @@ -81,6 +81,8 @@ class StackUtil { jobject** owned_monitors_ptr); static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth); + + static jvmtiError PopFrame(jvmtiEnv* env, jthread thread); }; struct FindFrameAtDepthVisitor : art::StackVisitor { @@ -110,6 +112,9 @@ struct FindFrameAtDepthVisitor : art::StackVisitor { } } + art::ShadowFrame* GetOrCreateShadowFrame(/*out*/bool* created_frame) + REQUIRES_SHARED(art::Locks::mutator_lock_); + private: bool found_frame_; size_t cnt_; diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc index b54c77da50..a0e5b5c926 100644 --- a/openjdkjvmti/ti_thread.cc +++ b/openjdkjvmti/ti_thread.cc @@ -623,18 +623,10 @@ jvmtiError ThreadUtil::GetAllThreads(jvmtiEnv* env, return ERR(NONE); } -// The struct that we store in the art::Thread::custom_tls_ that maps the jvmtiEnvs to the data -// stored with that thread. This is needed since different jvmtiEnvs are not supposed to share TLS -// data but we only have a single slot in Thread objects to store data. -struct JvmtiGlobalTLSData : public art::TLSData { - std::unordered_map<jvmtiEnv*, const void*> data GUARDED_BY(art::Locks::thread_list_lock_); -}; - static void RemoveTLSData(art::Thread* target, void* ctx) REQUIRES(art::Locks::thread_list_lock_) { jvmtiEnv* env = reinterpret_cast<jvmtiEnv*>(ctx); art::Locks::thread_list_lock_->AssertHeld(art::Thread::Current()); - JvmtiGlobalTLSData* global_tls = - reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey)); + JvmtiGlobalTLSData* global_tls = ThreadUtil::GetGlobalTLSData(target); if (global_tls != nullptr) { global_tls->data.erase(env); } @@ -657,19 +649,27 @@ jvmtiError ThreadUtil::SetThreadLocalStorage(jvmtiEnv* env, jthread thread, cons return err; } - JvmtiGlobalTLSData* global_tls = - reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey)); - if (global_tls == nullptr) { - // Synchronized using thread_list_lock_ to prevent racing sets. - target->SetCustomTLS(kJvmtiTlsKey, new JvmtiGlobalTLSData); - global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey)); - } + JvmtiGlobalTLSData* global_tls = GetOrCreateGlobalTLSData(target); global_tls->data[env] = data; return ERR(NONE); } +JvmtiGlobalTLSData* ThreadUtil::GetOrCreateGlobalTLSData(art::Thread* thread) { + JvmtiGlobalTLSData* data = GetGlobalTLSData(thread); + if (data != nullptr) { + return data; + } else { + thread->SetCustomTLS(kJvmtiTlsKey, new JvmtiGlobalTLSData); + return GetGlobalTLSData(thread); + } +} + +JvmtiGlobalTLSData* ThreadUtil::GetGlobalTLSData(art::Thread* thread) { + return reinterpret_cast<JvmtiGlobalTLSData*>(thread->GetCustomTLS(kJvmtiTlsKey)); +} + jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr) { @@ -686,8 +686,7 @@ jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env, return err; } - JvmtiGlobalTLSData* global_tls = - reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey)); + JvmtiGlobalTLSData* global_tls = GetGlobalTLSData(target); if (global_tls == nullptr) { *data_ptr = nullptr; return OK; diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h index c6b6af1035..39f1f0725c 100644 --- a/openjdkjvmti/ti_thread.h +++ b/openjdkjvmti/ti_thread.h @@ -32,11 +32,14 @@ #ifndef ART_OPENJDKJVMTI_TI_THREAD_H_ #define ART_OPENJDKJVMTI_TI_THREAD_H_ +#include <unordered_map> + #include "jni.h" #include "jvmti.h" #include "base/macros.h" #include "base/mutex.h" +#include "thread.h" namespace art { class ArtField; @@ -49,6 +52,18 @@ namespace openjdkjvmti { class EventHandler; +// The struct that we store in the art::Thread::custom_tls_ that maps the jvmtiEnvs to the data +// stored with that thread. This is needed since different jvmtiEnvs are not supposed to share TLS +// data but we only have a single slot in Thread objects to store data. +struct JvmtiGlobalTLSData : public art::TLSData { + std::unordered_map<jvmtiEnv*, const void*> data GUARDED_BY(art::Locks::thread_list_lock_); + + // The depth of the last frame where popping using PopFrame it is not allowed. It is set to + // kNoDisallowedPopFrame if all frames can be popped. See b/117615146 for more information. + static constexpr size_t kNoDisallowedPopFrame = -1; + size_t disable_pop_frame_depth = kNoDisallowedPopFrame; +}; + class ThreadUtil { public: static void Register(EventHandler* event_handler); @@ -134,6 +149,11 @@ class ThreadUtil { REQUIRES(!art::Locks::user_code_suspension_lock_, !art::Locks::thread_suspend_count_lock_); + static JvmtiGlobalTLSData* GetGlobalTLSData(art::Thread* thread) + REQUIRES(art::Locks::thread_list_lock_); + static JvmtiGlobalTLSData* GetOrCreateGlobalTLSData(art::Thread* thread) + REQUIRES(art::Locks::thread_list_lock_); + private: // We need to make sure only one thread tries to suspend threads at a time so we can get the // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h index c29043e7c6..15ab5f0387 100644 --- a/runtime/common_dex_operations.h +++ b/runtime/common_dex_operations.h @@ -27,6 +27,7 @@ #include "dex/primitive.h" #include "handle_scope-inl.h" #include "instrumentation.h" +#include "interpreter/interpreter.h" #include "interpreter/shadow_frame.h" #include "interpreter/unstarted_runtime.h" #include "jvalue-inl.h" @@ -172,6 +173,14 @@ ALWAYS_INLINE bool DoFieldPutCommon(Thread* self, if (UNLIKELY(self->IsExceptionPending())) { return false; } + if (shadow_frame.GetForcePopFrame()) { + // We need to check this here since we expect that the FieldWriteEvent happens before the + // actual field write. If one pops the stack we should not modify the field. The next + // instruction will force a pop. Return true. + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); + DCHECK(interpreter::PrevFrameWillRetry(self, shadow_frame)); + return true; + } } switch (field_type) { diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index fccfce4589..84631c377e 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -753,6 +753,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, const char* shorty = non_proxy_method->GetShorty(&shorty_len); JValue result; + bool force_frame_pop = false; if (UNLIKELY(deopt_frame != nullptr)) { HandleDeoptimization(&result, method, deopt_frame, &fragment); @@ -788,6 +789,7 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, } result = interpreter::EnterInterpreterFromEntryPoint(self, accessor, shadow_frame); + force_frame_pop = shadow_frame->GetForcePopFrame(); } // Pop transition. @@ -804,12 +806,20 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method " << caller->PrettyMethod(); } else { + VLOG(deopt) << "Forcing deoptimization on return from method " << method->PrettyMethod() + << " to " << caller->PrettyMethod() + << (force_frame_pop ? " for frame-pop" : ""); + DCHECK(!force_frame_pop || result.GetJ() == 0) << "Force frame pop should have no result."; + if (force_frame_pop && self->GetException() != nullptr) { + LOG(WARNING) << "Suppressing exception for instruction-retry: " + << self->GetException()->Dump(); + } // Push the context of the deoptimization stack so we can restore the return value and the // exception before executing the deoptimized frames. self->PushDeoptimizationContext( result, shorty[0] == 'L' || shorty[0] == '[', /* class or array */ - self->GetException(), + force_frame_pop ? nullptr : self->GetException(), false /* from_code */, DeoptimizationMethodType::kDefault); diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index df66061d01..2ae95dcc41 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -261,6 +261,12 @@ static inline JValue Execute( shadow_frame.GetThisObject(accessor.InsSize()), method, 0); + if (UNLIKELY(shadow_frame.GetForcePopFrame())) { + // The caller will retry this invoke. Just return immediately without any value. + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); + DCHECK(PrevFrameWillRetry(self, shadow_frame)); + return JValue(); + } if (UNLIKELY(self->IsExceptionPending())) { instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(accessor.InsSize()), @@ -494,8 +500,8 @@ void EnterInterpreterFromDeoptimize(Thread* self, JValue value; // Set value to last known result in case the shadow frame chain is empty. value.SetJ(ret_val->GetJ()); - // Are we executing the first shadow frame? - bool first = true; + // How many frames we have executed. + size_t frame_cnt = 0; while (shadow_frame != nullptr) { // We do not want to recover lock state for lock counting when deoptimizing. Currently, // the compiler should not have compiled a method that failed structured-locking checks. @@ -510,24 +516,30 @@ void EnterInterpreterFromDeoptimize(Thread* self, // the instrumentation. To prevent from reporting it a second time, we simply pass a // null Instrumentation*. const instrumentation::Instrumentation* const instrumentation = - first ? nullptr : Runtime::Current()->GetInstrumentation(); + frame_cnt == 0 ? nullptr : Runtime::Current()->GetInstrumentation(); new_dex_pc = MoveToExceptionHandler( self, *shadow_frame, instrumentation) ? shadow_frame->GetDexPC() : dex::kDexNoIndex; } else if (!from_code) { // Deoptimization is not called from code directly. const Instruction* instr = &accessor.InstructionAt(dex_pc); - if (deopt_method_type == DeoptimizationMethodType::kKeepDexPc) { - DCHECK(first); + if (deopt_method_type == DeoptimizationMethodType::kKeepDexPc || + shadow_frame->GetForceRetryInstruction()) { + DCHECK(frame_cnt == 0 || (frame_cnt == 1 && shadow_frame->GetForceRetryInstruction())) + << "frame_cnt: " << frame_cnt + << " force-retry: " << shadow_frame->GetForceRetryInstruction(); // Need to re-execute the dex instruction. // (1) An invocation might be split into class initialization and invoke. // In this case, the invoke should not be skipped. // (2) A suspend check should also execute the dex instruction at the // corresponding dex pc. + // If the ForceRetryInstruction bit is set this must be the second frame (the first being + // the one that is being popped). DCHECK_EQ(new_dex_pc, dex_pc); + shadow_frame->SetForceRetryInstruction(false); } else if (instr->Opcode() == Instruction::MONITOR_ENTER || instr->Opcode() == Instruction::MONITOR_EXIT) { DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault); - DCHECK(first); + DCHECK_EQ(frame_cnt, 0u); // Non-idempotent dex instruction should not be re-executed. // On the other hand, if a MONITOR_ENTER is at the dex_pc of a suspend // check, that MONITOR_ENTER should be executed. That case is handled @@ -553,7 +565,7 @@ void EnterInterpreterFromDeoptimize(Thread* self, DCHECK_EQ(new_dex_pc, dex_pc); } else { DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault); - DCHECK(first); + DCHECK_EQ(frame_cnt, 0u); // By default, we re-execute the dex instruction since if they are not // an invoke, so that we don't have to decode the dex instruction to move // result into the right vreg. All slow paths have been audited to be @@ -566,7 +578,7 @@ void EnterInterpreterFromDeoptimize(Thread* self, } else { // Nothing to do, the dex_pc is the one at which the code requested // the deoptimization. - DCHECK(first); + DCHECK_EQ(frame_cnt, 0u); DCHECK_EQ(new_dex_pc, dex_pc); } if (new_dex_pc != dex::kDexNoIndex) { @@ -585,7 +597,7 @@ void EnterInterpreterFromDeoptimize(Thread* self, // and should advance dex pc past the invoke instruction. from_code = false; deopt_method_type = DeoptimizationMethodType::kDefault; - first = false; + frame_cnt++; } ret_val->SetJ(value.GetJ()); } @@ -657,5 +669,18 @@ void InitInterpreterTls(Thread* self) { InitMterpTls(self); } +bool PrevFrameWillRetry(Thread* self, const ShadowFrame& frame) { + ShadowFrame* prev_frame = frame.GetLink(); + if (prev_frame == nullptr) { + NthCallerVisitor vis(self, 1, false); + vis.WalkStack(); + prev_frame = vis.GetCurrentShadowFrame(); + if (prev_frame == nullptr) { + prev_frame = self->FindDebuggerShadowFrame(vis.GetFrameId()); + } + } + return prev_frame != nullptr && prev_frame->GetForceRetryInstruction(); +} + } // namespace interpreter } // namespace art diff --git a/runtime/interpreter/interpreter.h b/runtime/interpreter/interpreter.h index 0d43b9090a..d7e69a6755 100644 --- a/runtime/interpreter/interpreter.h +++ b/runtime/interpreter/interpreter.h @@ -69,6 +69,12 @@ void CheckInterpreterAsmConstants(); void InitInterpreterTls(Thread* self); +// Returns true if the previous frame has the ForceRetryInstruction bit set. This is required for +// ForPopFrame to work correctly since that will cause the java function return with null/0 which +// might not be expected by the code being run. +bool PrevFrameWillRetry(Thread* self, const ShadowFrame& frame) + REQUIRES_SHARED(Locks::mutator_lock_); + } // namespace interpreter } // namespace art diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 657772619c..fe412bc1b1 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -371,6 +371,12 @@ bool DoIPutQuick(const ShadowFrame& shadow_frame, const Instruction* inst, uint1 if (UNLIKELY(self->IsExceptionPending())) { return false; } + if (UNLIKELY(shadow_frame.GetForcePopFrame())) { + // Don't actually set the field. The next instruction will force us to pop. + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); + DCHECK(PrevFrameWillRetry(self, shadow_frame)); + return true; + } } // Note: iput-x-quick instructions are only for non-volatile fields. switch (field_type) { @@ -440,6 +446,11 @@ bool MoveToExceptionHandler(Thread* self, self->IsExceptionThrownByCurrentMethod(exception.Get())) { // See b/65049545 for why we don't need to check to see if the exception has changed. instrumentation->ExceptionThrownEvent(self, exception.Get()); + if (shadow_frame.GetForcePopFrame()) { + // We will check in the caller for GetForcePopFrame again. We need to bail out early to + // prevent an ExceptionHandledEvent from also being sent before popping. + return true; + } } bool clear_exception = false; uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock( diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index 04935cfe42..cb64ff402a 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -24,16 +24,39 @@ #include "interpreter_common.h" #include "jit/jit.h" #include "jvalue-inl.h" +#include "nth_caller_visitor.h" #include "safe_math.h" #include "shadow_frame-inl.h" +#include "thread.h" namespace art { namespace interpreter { +#define CHECK_FORCE_RETURN() \ + do { \ + if (UNLIKELY(shadow_frame.GetForcePopFrame())) { \ + DCHECK(PrevFrameWillRetry(self, shadow_frame)) \ + << "Pop frame forced without previous frame ready to retry instruction!"; \ + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); \ + if (UNLIKELY(NeedsMethodExitEvent(instrumentation))) { \ + SendMethodExitEvents(self, \ + instrumentation, \ + shadow_frame, \ + shadow_frame.GetThisObject(accessor.InsSize()), \ + shadow_frame.GetMethod(), \ + inst->GetDexPc(insns), \ + JValue()); \ + } \ + ctx->result = JValue(); /* Handled in caller. */ \ + return; \ + } \ + } while (false) + #define HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instr) \ do { \ DCHECK(self->IsExceptionPending()); \ self->AllowThreadSuspension(); \ + CHECK_FORCE_RETURN(); \ if (!MoveToExceptionHandler(self, shadow_frame, instr)) { \ /* Structured locking is to be enforced for abnormal termination, too. */ \ DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); \ @@ -44,6 +67,7 @@ namespace interpreter { ctx->result = JValue(); /* Handled in caller. */ \ return; \ } else { \ + CHECK_FORCE_RETURN(); \ int32_t displacement = \ static_cast<int32_t>(shadow_frame.GetDexPC()) - static_cast<int32_t>(dex_pc); \ inst = inst->RelativeAt(displacement); \ @@ -52,8 +76,39 @@ namespace interpreter { #define HANDLE_PENDING_EXCEPTION() HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instrumentation) +#define POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_IMPL(_is_exception_pending, _next_function) \ + do { \ + if (UNLIKELY(shadow_frame.GetForceRetryInstruction())) { \ + /* Don't need to do anything except clear the flag and exception. We leave the */ \ + /* instruction the same so it will be re-executed on the next go-around. */ \ + DCHECK(inst->IsInvoke()); \ + shadow_frame.SetForceRetryInstruction(false); \ + if (UNLIKELY(_is_exception_pending)) { \ + DCHECK(self->IsExceptionPending()); \ + if (kIsDebugBuild) { \ + LOG(WARNING) << "Suppressing exception for instruction-retry: " \ + << self->GetException()->Dump(); \ + } \ + self->ClearException(); \ + } \ + } else if (UNLIKELY(_is_exception_pending)) { \ + /* Should have succeeded. */ \ + DCHECK(!shadow_frame.GetForceRetryInstruction()); \ + HANDLE_PENDING_EXCEPTION(); \ + } else { \ + inst = inst->_next_function(); \ + } \ + } while (false) + +#define POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_POLYMORPHIC(_is_exception_pending) \ + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_IMPL(_is_exception_pending, Next_4xx) +#define POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(_is_exception_pending) \ + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_IMPL(_is_exception_pending, Next_3xx) + #define POSSIBLY_HANDLE_PENDING_EXCEPTION(_is_exception_pending, _next_function) \ do { \ + /* Should only be on invoke instructions. */ \ + DCHECK(!shadow_frame.GetForceRetryInstruction()); \ if (UNLIKELY(_is_exception_pending)) { \ HANDLE_PENDING_EXCEPTION(); \ } else { \ @@ -67,17 +122,22 @@ namespace interpreter { } // Code to run before each dex instruction. -#define PREAMBLE_SAVE(save_ref) \ +#define PREAMBLE_SAVE(save_ref) \ { \ - if (UNLIKELY(instrumentation->HasDexPcListeners()) && \ - UNLIKELY(!DoDexPcMoveEvent(self, \ - accessor, \ - shadow_frame, \ - dex_pc, \ - instrumentation, \ - save_ref))) { \ - HANDLE_PENDING_EXCEPTION(); \ - break; \ + /* We need to put this before & after the instrumentation to avoid having to put in a */ \ + /* post-script macro. */ \ + CHECK_FORCE_RETURN(); \ + if (UNLIKELY(instrumentation->HasDexPcListeners())) { \ + if (UNLIKELY(!DoDexPcMoveEvent(self, \ + accessor, \ + shadow_frame, \ + dex_pc, \ + instrumentation, \ + save_ref))) { \ + HANDLE_PENDING_EXCEPTION(); \ + break; \ + } \ + CHECK_FORCE_RETURN(); \ } \ } \ do {} while (false) @@ -181,7 +241,8 @@ NO_INLINE static bool SendMethodExitEvents(Thread* self, const JValue& result) REQUIRES_SHARED(Locks::mutator_lock_) { bool had_event = false; - if (UNLIKELY(instrumentation->HasMethodExitListeners())) { + // We don't send method-exit if it's a pop-frame. We still send frame_popped though. + if (UNLIKELY(instrumentation->HasMethodExitListeners() && !frame.GetForcePopFrame())) { had_event = true; instrumentation->MethodExitEvent(self, thiz.Ptr(), method, dex_pc, result); } @@ -221,6 +282,9 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) uint16_t inst_data; jit::Jit* jit = Runtime::Current()->GetJit(); + DCHECK(!shadow_frame.GetForceRetryInstruction()) + << "Entered interpreter from invoke without retry instruction being handled!"; + do { dex_pc = inst->GetDexPc(insns); shadow_frame.SetDexPC(dex_pc); @@ -1607,84 +1671,84 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) PREAMBLE(); bool success = DoInvoke<kVirtual, false, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_VIRTUAL_RANGE: { PREAMBLE(); bool success = DoInvoke<kVirtual, true, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_SUPER: { PREAMBLE(); bool success = DoInvoke<kSuper, false, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_SUPER_RANGE: { PREAMBLE(); bool success = DoInvoke<kSuper, true, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_DIRECT: { PREAMBLE(); bool success = DoInvoke<kDirect, false, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_DIRECT_RANGE: { PREAMBLE(); bool success = DoInvoke<kDirect, true, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_INTERFACE: { PREAMBLE(); bool success = DoInvoke<kInterface, false, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_INTERFACE_RANGE: { PREAMBLE(); bool success = DoInvoke<kInterface, true, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_STATIC: { PREAMBLE(); bool success = DoInvoke<kStatic, false, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_STATIC_RANGE: { PREAMBLE(); bool success = DoInvoke<kStatic, true, do_access_check>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_VIRTUAL_QUICK: { PREAMBLE(); bool success = DoInvokeVirtualQuick<false>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: { PREAMBLE(); bool success = DoInvokeVirtualQuick<true>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_POLYMORPHIC: { @@ -1692,7 +1756,7 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) DCHECK(Runtime::Current()->IsMethodHandlesEnabled()); bool success = DoInvokePolymorphic<false /* is_range */>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_4xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_POLYMORPHIC(!success); break; } case Instruction::INVOKE_POLYMORPHIC_RANGE: { @@ -1700,7 +1764,7 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) DCHECK(Runtime::Current()->IsMethodHandlesEnabled()); bool success = DoInvokePolymorphic<true /* is_range */>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_4xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_POLYMORPHIC(!success); break; } case Instruction::INVOKE_CUSTOM: { @@ -1708,7 +1772,7 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) DCHECK(Runtime::Current()->IsMethodHandlesEnabled()); bool success = DoInvokeCustom<false /* is_range */>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::INVOKE_CUSTOM_RANGE: { @@ -1716,7 +1780,7 @@ ATTRIBUTE_NO_SANITIZE_ADDRESS void ExecuteSwitchImplCpp(SwitchImplContext* ctx) DCHECK(Runtime::Current()->IsMethodHandlesEnabled()); bool success = DoInvokeCustom<true /* is_range */>( self, shadow_frame, inst, inst_data, &result_register); - POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx); + POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success); break; } case Instruction::NEG_INT: diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc index fbc96f7e18..c385fb9417 100644 --- a/runtime/interpreter/mterp/mterp.cc +++ b/runtime/interpreter/mterp/mterp.cc @@ -152,6 +152,11 @@ extern "C" size_t MterpShouldSwitchInterpreters() const instrumentation::Instrumentation* const instrumentation = runtime->GetInstrumentation(); return instrumentation->NonJitProfilingActive() || Dbg::IsDebuggerActive() || + // mterp only knows how to deal with the normal exits. It cannot handle any of the + // non-standard force-returns. + // TODO We really only need to switch interpreters if a PopFrame has actually happened. We + // should check this here. + UNLIKELY(runtime->AreNonStandardExitsEnabled()) || // An async exception has been thrown. We need to go to the switch interpreter. MTerp doesn't // know how to deal with these so we could end up never dealing with it if we are in an // infinite loop. Since this can be called in a tight loop and getting the current thread diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h index 88eb413ec7..c0920a8466 100644 --- a/runtime/interpreter/shadow_frame.h +++ b/runtime/interpreter/shadow_frame.h @@ -49,6 +49,17 @@ using ShadowFrameAllocaUniquePtr = std::unique_ptr<ShadowFrame, ShadowFrameDelet // - interpreter - separate VRegs and reference arrays. References are in the reference array. // - JNI - just VRegs, but where every VReg holds a reference. class ShadowFrame { + private: + // Used to keep track of extra state the shadowframe has. + enum class FrameFlags : uint32_t { + // We have been requested to notify when this frame gets popped. + kNotifyFramePop = 1 << 0, + // We have been asked to pop this frame off the stack as soon as possible. + kForcePopFrame = 1 << 1, + // We have been asked to re-execute the last instruction. + kForceRetryInst = 1 << 2, + }; + public: // Compute size of ShadowFrame in bytes assuming it has a reference array. static size_t ComputeSize(uint32_t num_vregs) { @@ -341,11 +352,27 @@ class ShadowFrame { } bool NeedsNotifyPop() const { - return needs_notify_pop_; + return GetFrameFlag(FrameFlags::kNotifyFramePop); } void SetNotifyPop(bool notify) { - needs_notify_pop_ = notify; + UpdateFrameFlag(notify, FrameFlags::kNotifyFramePop); + } + + bool GetForcePopFrame() const { + return GetFrameFlag(FrameFlags::kForcePopFrame); + } + + void SetForcePopFrame(bool enable) { + UpdateFrameFlag(enable, FrameFlags::kForcePopFrame); + } + + bool GetForceRetryInstruction() const { + return GetFrameFlag(FrameFlags::kForceRetryInst); + } + + void SetForceRetryInstruction(bool enable) { + UpdateFrameFlag(enable, FrameFlags::kForceRetryInst); } private: @@ -360,7 +387,7 @@ class ShadowFrame { dex_pc_(dex_pc), cached_hotness_countdown_(0), hotness_countdown_(0), - needs_notify_pop_(0) { + frame_flags_(0) { // TODO(iam): Remove this parameter, it's an an artifact of portable removal DCHECK(has_reference_array); if (has_reference_array) { @@ -370,6 +397,18 @@ class ShadowFrame { } } + void UpdateFrameFlag(bool enable, FrameFlags flag) { + if (enable) { + frame_flags_ |= static_cast<uint32_t>(flag); + } else { + frame_flags_ &= ~static_cast<uint32_t>(flag); + } + } + + bool GetFrameFlag(FrameFlags flag) const { + return (frame_flags_ & static_cast<uint32_t>(flag)) != 0; + } + const StackReference<mirror::Object>* References() const { DCHECK(HasReferenceArray()); const uint32_t* vreg_end = &vregs_[NumberOfVRegs()]; @@ -393,9 +432,11 @@ class ShadowFrame { uint32_t dex_pc_; int16_t cached_hotness_countdown_; int16_t hotness_countdown_; - // TODO Might be worth it to try to bit-pack this into some other field to reduce stack usage. - // NB alignment requires that this field takes 4 bytes. Only 1 bit is actually ever used. - bool needs_notify_pop_; + + // This is a set of ShadowFrame::FrameFlags which denote special states this frame is in. + // NB alignment requires that this field takes 4 bytes no matter its size. Only 3 bits are + // currently used. + uint32_t frame_flags_; // This is a two-part array: // - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4 diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index e882e7370a..36a6b7fc47 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -402,6 +402,8 @@ class DeoptimizeStackVisitor final : public StackVisitor { bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) { exception_handler_->SetHandlerFrameDepth(GetFrameDepth()); ArtMethod* method = GetMethod(); + VLOG(deopt) << "Deoptimizing stack: depth: " << GetFrameDepth() + << " at method " << ArtMethod::PrettyMethod(method); if (method == nullptr || single_frame_done_) { FinishStackWalk(); return false; // End stack walk. diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 00d1d5993e..027193765a 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -256,6 +256,7 @@ Runtime::Runtime() is_native_bridge_loaded_(false), is_native_debuggable_(false), async_exceptions_thrown_(false), + non_standard_exits_enabled_(false), is_java_debuggable_(false), zygote_max_failed_boots_(0), experimental_flags_(ExperimentalFlags::kNone), diff --git a/runtime/runtime.h b/runtime/runtime.h index 478ff502d9..398a48d935 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -654,6 +654,14 @@ class Runtime { is_native_debuggable_ = value; } + bool AreNonStandardExitsEnabled() const { + return non_standard_exits_enabled_; + } + + void SetNonStandardExitsEnabled() { + non_standard_exits_enabled_ = true; + } + bool AreAsyncExceptionsThrown() const { return async_exceptions_thrown_; } @@ -986,6 +994,10 @@ class Runtime { // MterpShouldSwitchInterpreters function. bool async_exceptions_thrown_; + // Whether anything is going to be using the shadow-frame APIs to force a function to return + // early. Doing this requires that (1) we be debuggable and (2) that mterp is exited. + bool non_standard_exits_enabled_; + // Whether Java code needs to be debuggable. bool is_java_debuggable_; diff --git a/runtime/thread.cc b/runtime/thread.cc index 0092b9720a..2fb63a1411 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -3368,11 +3368,34 @@ void Thread::QuickDeliverException() { HandleWrapperObjPtr<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception)); instrumentation->ExceptionThrownEvent(this, exception.Ptr()); } - // Does instrumentation need to deoptimize the stack? - // Note: we do this *after* reporting the exception to instrumentation in case it - // now requires deoptimization. It may happen if a debugger is attached and requests - // new events (single-step, breakpoint, ...) when the exception is reported. - if (Dbg::IsForcedInterpreterNeededForException(this)) { + // Does instrumentation need to deoptimize the stack or otherwise go to interpreter for something? + // Note: we do this *after* reporting the exception to instrumentation in case it now requires + // deoptimization. It may happen if a debugger is attached and requests new events (single-step, + // breakpoint, ...) when the exception is reported. + ShadowFrame* cf; + bool force_frame_pop = false; + { + NthCallerVisitor visitor(this, 0, false); + visitor.WalkStack(); + cf = visitor.GetCurrentShadowFrame(); + if (cf == nullptr) { + cf = FindDebuggerShadowFrame(visitor.GetFrameId()); + } + force_frame_pop = cf != nullptr && cf->GetForcePopFrame(); + if (kIsDebugBuild && force_frame_pop) { + NthCallerVisitor penultimate_visitor(this, 1, false); + penultimate_visitor.WalkStack(); + ShadowFrame* penultimate_frame = penultimate_visitor.GetCurrentShadowFrame(); + if (penultimate_frame == nullptr) { + penultimate_frame = FindDebuggerShadowFrame(penultimate_visitor.GetFrameId()); + } + DCHECK(penultimate_frame != nullptr && + penultimate_frame->GetForceRetryInstruction()) + << "Force pop frame without retry instruction found. penultimate frame is null: " + << (penultimate_frame == nullptr ? "true" : "false"); + } + } + if (Dbg::IsForcedInterpreterNeededForException(this) || force_frame_pop) { NthCallerVisitor visitor(this, 0, false); visitor.WalkStack(); if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) { @@ -3380,10 +3403,16 @@ void Thread::QuickDeliverException() { const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault; // Save the exception into the deoptimization context so it can be restored // before entering the interpreter. + if (force_frame_pop) { + VLOG(deopt) << "Deopting " << cf->GetMethod()->PrettyMethod() << " for frame-pop"; + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); + // Get rid of the exception since we are doing a framepop instead. + ClearException(); + } PushDeoptimizationContext( JValue(), false /* is_reference */, - exception, + (force_frame_pop ? nullptr : exception), false /* from_code */, method_type); artDeoptimize(this); diff --git a/test/1953-pop-frame/check b/test/1953-pop-frame/check new file mode 100755 index 0000000000..d552272bca --- /dev/null +++ b/test/1953-pop-frame/check @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (C) 2018 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. + +# The RI has restrictions and bugs around some PopFrame behavior that ART lacks. +# See b/116003018. Some configurations cannot handle the class load events in +# quite the right way so they are disabled there too. +./default-check "$@" || \ + (patch -p0 expected.txt < class-loading-expected.patch >/dev/null && ./default-check "$@") diff --git a/test/1953-pop-frame/class-loading-expected.patch b/test/1953-pop-frame/class-loading-expected.patch new file mode 100644 index 0000000000..2edef15dfd --- /dev/null +++ b/test/1953-pop-frame/class-loading-expected.patch @@ -0,0 +1,21 @@ +74a75,94 +> Test stopped during a ClassLoad event. +> Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +> Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +> art.Test1953.popFrame(Native Method) +> art.Test1953.runTestOn(Test1953.java) +> art.Test1953.runTestOn(Test1953.java) +> art.Test1953.runTests(Test1953.java) +> <Additional frames hidden> +> TC0.foo == 1 +> result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +> Test stopped during a ClassPrepare event. +> Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +> Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +> art.Test1953.popFrame(Native Method) +> art.Test1953.runTestOn(Test1953.java) +> art.Test1953.runTestOn(Test1953.java) +> art.Test1953.runTests(Test1953.java) +> <Additional frames hidden> +> TC1.foo == 2 +> result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 diff --git a/test/1953-pop-frame/expected.txt b/test/1953-pop-frame/expected.txt new file mode 100644 index 0000000000..906703d715 --- /dev/null +++ b/test/1953-pop-frame/expected.txt @@ -0,0 +1,98 @@ +Test stopped using breakpoint +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with declared synchronized function +Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with synchronized block +Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedTestObject { cnt: 2 } base-call count: 1 +Test stopped on single step +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped on field access +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped on field modification +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped during Method Exit of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Enter of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during Method Enter of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit due to exception thrown in same function +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1 +Test stopped during Method Exit due to exception thrown in subroutine +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of calledFunction +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of doThrow +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in calling function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in parent of calling function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught! +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function. +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during random Suspend. +Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0 +result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1 +Test redefining frame being popped. +Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0 +result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1 +Test stopped during a native method fails +Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCalledObject { cnt: 1 } base-call count: 1 +Test stopped in a method called by native fails +Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCallerObject { cnt: 1 } base-call count: 1 diff --git a/test/1953-pop-frame/info.txt b/test/1953-pop-frame/info.txt new file mode 100644 index 0000000000..b5eb5464b7 --- /dev/null +++ b/test/1953-pop-frame/info.txt @@ -0,0 +1,7 @@ +Test basic JVMTI breakpoint functionality. + +This test places a breakpoint on the first instruction of a number of functions +that are entered in every way possible for the given class of method. + +It also tests that breakpoints don't interfere with each other by having +multiple breakpoints be set at once. diff --git a/test/1953-pop-frame/pop_frame.cc b/test/1953-pop-frame/pop_frame.cc new file mode 100644 index 0000000000..1c2d2a145a --- /dev/null +++ b/test/1953-pop-frame/pop_frame.cc @@ -0,0 +1,998 @@ +/* + * Copyright (C) 2013 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. + */ + +#include <inttypes.h> + +#include <cstdio> +#include <memory> +#include <string> +#include <vector> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" +#include "scoped_utf_chars.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test1953PopFrame { + +struct TestData { + jlocation target_loc; + jmethodID target_method; + jclass target_klass; + jfieldID target_field; + jrawMonitorID notify_monitor; + jint frame_pop_offset; + jmethodID frame_pop_setup_method; + std::vector<std::string> interesting_classes; + bool hit_location; + + TestData(jvmtiEnv* jvmti, + JNIEnv* env, + jlocation loc, + jobject meth, + jclass klass, + jobject field, + jobject setup_meth, + jint pop_offset, + const std::vector<std::string>&& interesting) + : target_loc(loc), + target_method(meth != nullptr ? env->FromReflectedMethod(meth) : nullptr), + target_klass(reinterpret_cast<jclass>(env->NewGlobalRef(klass))), + target_field(field != nullptr ? env->FromReflectedField(field) : nullptr), + frame_pop_offset(pop_offset), + frame_pop_setup_method(setup_meth != nullptr ? env->FromReflectedMethod(setup_meth) + : nullptr), + interesting_classes(interesting), + hit_location(false) { + JvmtiErrorToException(env, jvmti, jvmti->CreateRawMonitor("SuspendStopMonitor", + ¬ify_monitor)); + } + + void PerformSuspend(jvmtiEnv* jvmti, JNIEnv* env) { + // Wake up the waiting thread. + JvmtiErrorToException(env, jvmti, jvmti->RawMonitorEnter(notify_monitor)); + hit_location = true; + JvmtiErrorToException(env, jvmti, jvmti->RawMonitorNotifyAll(notify_monitor)); + JvmtiErrorToException(env, jvmti, jvmti->RawMonitorExit(notify_monitor)); + // Suspend ourself + jvmti->SuspendThread(nullptr); + } +}; + +void JNICALL cbSingleStep(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thr, + jmethodID meth, + jlocation loc) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (meth != data->target_method || loc != data->target_loc) { + return; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbExceptionCatch(jvmtiEnv *jvmti, + JNIEnv* env, + jthread thr, + jmethodID method, + jlocation location ATTRIBUTE_UNUSED, + jobject exception ATTRIBUTE_UNUSED) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (method != data->target_method) { + return; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbException(jvmtiEnv *jvmti, + JNIEnv* env, + jthread thr, + jmethodID method, + jlocation location ATTRIBUTE_UNUSED, + jobject exception ATTRIBUTE_UNUSED, + jmethodID catch_method ATTRIBUTE_UNUSED, + jlocation catch_location ATTRIBUTE_UNUSED) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (method != data->target_method) { + return; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbMethodEntry(jvmtiEnv *jvmti, + JNIEnv* env, + jthread thr, + jmethodID method) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (method != data->target_method) { + return; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbMethodExit(jvmtiEnv *jvmti, + JNIEnv* env, + jthread thr, + jmethodID method, + jboolean was_popped_by_exception ATTRIBUTE_UNUSED, + jvalue return_value ATTRIBUTE_UNUSED) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (method != data->target_method) { + return; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbFieldModification(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thr, + jmethodID method ATTRIBUTE_UNUSED, + jlocation location ATTRIBUTE_UNUSED, + jclass field_klass ATTRIBUTE_UNUSED, + jobject object ATTRIBUTE_UNUSED, + jfieldID field, + char signature_type ATTRIBUTE_UNUSED, + jvalue new_value ATTRIBUTE_UNUSED) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (field != data->target_field) { + // TODO What to do here. + LOG(FATAL) << "Strange, shouldn't get here!"; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbFieldAccess(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thr, + jmethodID method ATTRIBUTE_UNUSED, + jlocation location ATTRIBUTE_UNUSED, + jclass field_klass, + jobject object ATTRIBUTE_UNUSED, + jfieldID field) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (field != data->target_field || !env->IsSameObject(field_klass, data->target_klass)) { + // TODO What to do here. + LOG(FATAL) << "Strange, shouldn't get here!"; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbBreakpointHit(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thr, + jmethodID method, + jlocation loc) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (data->frame_pop_setup_method == method) { + CHECK(loc == 0) << "We should have stopped at location 0"; + if (JvmtiErrorToException(env, + jvmti, + jvmti->NotifyFramePop(thr, data->frame_pop_offset))) { + return; + } + return; + } + if (method != data->target_method || loc != data->target_loc) { + // TODO What to do here. + LOG(FATAL) << "Strange, shouldn't get here!"; + } + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbFramePop(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thr, + jmethodID method ATTRIBUTE_UNUSED, + jboolean was_popped_by_exception ATTRIBUTE_UNUSED) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + data->PerformSuspend(jvmti, env); +} + +void JNICALL cbClassLoadOrPrepare(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thr, + jclass klass) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti, + jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + char* name; + if (JvmtiErrorToException(env, jvmti, jvmti->GetClassSignature(klass, &name, nullptr))) { + return; + } + std::string name_str(name); + if (JvmtiErrorToException(env, + jvmti, + jvmti->Deallocate(reinterpret_cast<unsigned char*>(name)))) { + return; + } + if (std::find(data->interesting_classes.cbegin(), + data->interesting_classes.cend(), + name_str) != data->interesting_classes.cend()) { + data->PerformSuspend(jvmti, env); + } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupTest(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + // Most of these will already be there but might as well be complete. + caps.can_pop_frame = 1; + caps.can_generate_single_step_events = 1; + caps.can_generate_breakpoint_events = 1; + caps.can_suspend = 1; + caps.can_generate_method_entry_events = 1; + caps.can_generate_method_exit_events = 1; + caps.can_generate_monitor_events = 1; + caps.can_generate_exception_events = 1; + caps.can_generate_frame_pop_events = 1; + caps.can_generate_field_access_events = 1; + caps.can_generate_field_modification_events = 1; + caps.can_redefine_classes = 1; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) { + return; + } + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + // TODO Add the rest of these. + cb.Breakpoint = cbBreakpointHit; + cb.SingleStep = cbSingleStep; + cb.FieldAccess = cbFieldAccess; + cb.FieldModification = cbFieldModification; + cb.MethodEntry = cbMethodEntry; + cb.MethodExit = cbMethodExit; + cb.Exception = cbException; + cb.ExceptionCatch = cbExceptionCatch; + cb.FramePop = cbFramePop; + cb.ClassLoad = cbClassLoadOrPrepare; + cb.ClassPrepare = cbClassLoadOrPrepare; + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb))); +} + +static bool DeleteTestData(JNIEnv* env, jthread thr, TestData* data) { + env->DeleteGlobalRef(data->target_klass); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, nullptr))) { + return false; + } + return JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Deallocate(reinterpret_cast<uint8_t*>(data))); +} + +static TestData* SetupTestData(JNIEnv* env, + jobject meth, + jlocation loc, + jclass target_klass, + jobject field, + jobject setup_meth, + jint pop_offset, + const std::vector<std::string>&& interesting_names) { + void* data_ptr; + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->Allocate(sizeof(TestData), + reinterpret_cast<uint8_t**>(&data_ptr)))) { + return nullptr; + } + data = new (data_ptr) TestData(jvmti_env, + env, + loc, + meth, + target_klass, + field, + setup_meth, + pop_offset, + std::move(interesting_names)); + if (env->ExceptionCheck()) { + env->DeleteGlobalRef(data->target_klass); + jvmti_env->Deallocate(reinterpret_cast<uint8_t*>(data)); + return nullptr; + } + return data; +} + +static TestData* SetupTestData(JNIEnv* env, + jobject meth, + jlocation loc, + jclass target_klass, + jobject field, + jobject setup_meth, + jint pop_offset) { + std::vector<std::string> empty; + return SetupTestData( + env, meth, loc, target_klass, field, setup_meth, pop_offset, std::move(empty)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupSuspendClassEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jint event_num, + jobjectArray interesting_names, + jthread thr) { + CHECK(event_num == JVMTI_EVENT_CLASS_LOAD || event_num == JVMTI_EVENT_CLASS_PREPARE); + std::vector<std::string> names; + jint cnt = env->GetArrayLength(interesting_names); + for (jint i = 0; i < cnt; i++) { + env->PushLocalFrame(1); + jstring name_obj = reinterpret_cast<jstring>(env->GetObjectArrayElement(interesting_names, i)); + const char* name_chr = env->GetStringUTFChars(name_obj, nullptr); + names.push_back(std::string(name_chr)); + env->ReleaseStringUTFChars(name_obj, name_chr); + env->PopLocalFrame(nullptr); + } + TestData* data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, nullptr, 0, nullptr, nullptr, nullptr, 0, std::move(names)); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + static_cast<jvmtiEvent>(event_num), + thr)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearSuspendClassEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_LOAD, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_PREPARE, + thr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupSuspendSingleStepAt(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject meth, + jlocation loc, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, meth, loc, nullptr, nullptr, nullptr, 0); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_SINGLE_STEP, + thr)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearSuspendSingleStepFor(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_SINGLE_STEP, + thr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupSuspendPopFrameEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jint offset, + jobject breakpoint_func, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, nullptr, 0, nullptr, nullptr, breakpoint_func, offset); + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_FRAME_POP, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetBreakpoint(data->frame_pop_setup_method, 0))) { + return; + } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearSuspendPopFrameEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FRAME_POP, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->ClearBreakpoint(data->frame_pop_setup_method, 0))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupSuspendBreakpointFor(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject meth, + jlocation loc, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, meth, loc, nullptr, nullptr, nullptr, 0); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(data->target_method, + data->target_loc)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearSuspendBreakpointFor(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_BREAKPOINT, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->ClearBreakpoint(data->target_method, + data->target_loc))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, nullptr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupSuspendExceptionEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject method, + jboolean is_catch, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage( + thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, method, 0, nullptr, nullptr, nullptr, 0); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, + is_catch ? JVMTI_EVENT_EXCEPTION_CATCH : JVMTI_EVENT_EXCEPTION, + thr)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearSuspendExceptionEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_EXCEPTION_CATCH, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_EXCEPTION, + thr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupSuspendMethodEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jobject method, + jboolean enter, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage( + thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, method, 0, nullptr, nullptr, nullptr, 0); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, + enter ? JVMTI_EVENT_METHOD_ENTRY : JVMTI_EVENT_METHOD_EXIT, + thr)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearSuspendMethodEvent(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_METHOD_EXIT, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_METHOD_ENTRY, + thr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupFieldSuspendFor(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jclass target_klass, + jobject field, + jboolean access, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage( + thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, nullptr, 0, target_klass, field, nullptr, 0); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode( + JVMTI_ENABLE, + access ? JVMTI_EVENT_FIELD_ACCESS : JVMTI_EVENT_FIELD_MODIFICATION, + thr))) { + return; + } + if (access) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(data->target_klass, + data->target_field)); + } else { + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(data->target_klass, + data->target_field)); + } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearFieldSuspendFor(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FIELD_ACCESS, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_FIELD_MODIFICATION, + thr))) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->ClearFieldModificationWatch( + data->target_klass, data->target_field)) && + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->ClearFieldAccessWatch( + data->target_klass, data->target_field))) { + return; + } else { + env->ExceptionClear(); + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, nullptr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_setupWaitForNativeCall(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage( + thr, reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data == nullptr) << "Data was not cleared!"; + data = SetupTestData(env, nullptr, 0, nullptr, nullptr, nullptr, 0); + if (data == nullptr) { + return; + } + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_clearWaitForNativeCall(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetThreadLocalStorage(thr, nullptr))) { + return; + } + DeleteTestData(env, thr, data); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_waitForSuspendHit(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(thr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(data->notify_monitor))) { + return; + } + while (!data->hit_location) { + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorWait(data->notify_monitor, -1))) { + return; + } + } + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(data->notify_monitor))) { + return; + } + jint state = 0; + while (!JvmtiErrorToException(env, jvmti_env, jvmti_env->GetThreadState(thr, &state)) && + (state & JVMTI_THREAD_STATE_SUSPENDED) == 0) { } +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_popFrame(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED, + jthread thr) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->PopFrame(thr)); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_00024NativeCalledObject_calledFunction( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(1); + jclass klass = env->GetObjectClass(thiz); + jfieldID cnt = env->GetFieldID(klass, "cnt", "I"); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + env->PopLocalFrame(nullptr); + TestData *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(/* thread */ nullptr, + reinterpret_cast<void**>(&data)))) { + return; + } + CHECK(data != nullptr); + data->PerformSuspend(jvmti_env, env); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1953_00024NativeCallerObject_run( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(1); + jclass klass = env->GetObjectClass(thiz); + jfieldID baseCnt = env->GetFieldID(klass, "baseCnt", "I"); + env->SetIntField(thiz, baseCnt, env->GetIntField(thiz, baseCnt) + 1); + jmethodID called = env->GetMethodID(klass, "calledFunction", "()V"); + env->CallVoidMethod(thiz, called); + env->PopLocalFrame(nullptr); +} + +extern "C" JNIEXPORT +jboolean JNICALL Java_art_Test1953_isClassLoaded(JNIEnv* env, jclass, jstring name) { + ScopedUtfChars chr(env, name); + if (env->ExceptionCheck()) { + return false; + } + jint cnt = 0; + jclass* klasses = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&cnt, &klasses))) { + return false; + } + bool res = false; + for (jint i = 0; !res && i < cnt; i++) { + char* sig; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetClassSignature(klasses[i], &sig, nullptr))) { + return false; + } + res = (strcmp(sig, chr.c_str()) == 0); + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(sig)); + } + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); + return res; +} + +} // namespace Test1953PopFrame +} // namespace art + diff --git a/test/1953-pop-frame/run b/test/1953-pop-frame/run new file mode 100755 index 0000000000..d16d4e6091 --- /dev/null +++ b/test/1953-pop-frame/run @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# On RI we need to turn class-load tests off since those events are buggy around +# pop-frame (see b/116003018). +ARGS="" +if [[ "$TEST_RUNTIME" == "jvm" ]]; then + ARGS="--args DISABLE_CLASS_LOAD_TESTS" +fi + +./default-run "$@" --jvmti $ARGS diff --git a/test/1953-pop-frame/src/Main.java b/test/1953-pop-frame/src/Main.java new file mode 100644 index 0000000000..156076e7a3 --- /dev/null +++ b/test/1953-pop-frame/src/Main.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017 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. + */ + +import java.util.Arrays; +import java.util.List; +public class Main { + public static void main(String[] args) throws Exception { + art.Test1953.run(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")); + } +} diff --git a/test/1953-pop-frame/src/art/Breakpoint.java b/test/1953-pop-frame/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1953-pop-frame/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1953-pop-frame/src/art/Redefinition.java b/test/1953-pop-frame/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/1953-pop-frame/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/1953-pop-frame/src/art/StackTrace.java b/test/1953-pop-frame/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1953-pop-frame/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1953-pop-frame/src/art/Suspension.java b/test/1953-pop-frame/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1953-pop-frame/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1953-pop-frame/src/art/Test1953.java b/test/1953-pop-frame/src/art/Test1953.java new file mode 100644 index 0000000000..adec7762b1 --- /dev/null +++ b/test/1953-pop-frame/src/art/Test1953.java @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Base64; +import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +public class Test1953 { + public final boolean canRunClassLoadTests; + public static void doNothing() {} + + public interface TestRunnable extends Runnable { + public int getBaseCallCount(); + public Method getCalledMethod() throws Exception; + public default Method getCallingMethod() throws Exception { + return this.getClass().getMethod("run"); + }; + } + + public static interface TestSuspender { + public void setup(Thread thr); + public void waitForSuspend(Thread thr); + public void cleanup(Thread thr); + } + + public static interface ThreadRunnable { public void run(Thread thr); } + public static TestSuspender makeSuspend(final ThreadRunnable setup, final ThreadRunnable clean) { + return new TestSuspender() { + public void setup(Thread thr) { setup.run(thr); } + public void waitForSuspend(Thread thr) { Test1953.waitForSuspendHit(thr); } + public void cleanup(Thread thr) { clean.run(thr); } + }; + } + + public void runTestOn(TestRunnable testObj, ThreadRunnable su, ThreadRunnable cl) throws + Exception { + runTestOn(testObj, makeSuspend(su, cl)); + } + + private static void SafePrintStackTrace(StackTraceElement st[]) { + for (StackTraceElement e : st) { + System.out.println("\t" + e.getClassName() + "." + e.getMethodName() + "(" + + (e.isNativeMethod() ? "Native Method" : e.getFileName()) + ")"); + if (e.getClassName().equals("art.Test1953") && e.getMethodName().equals("runTests")) { + System.out.println("\t<Additional frames hidden>"); + break; + } + } + } + + public void runTestOn(TestRunnable testObj, TestSuspender su) throws Exception { + System.out.println("Single call with PopFrame on " + testObj + " base-call-count: " + + testObj.getBaseCallCount()); + final CountDownLatch continue_latch = new CountDownLatch(1); + final CountDownLatch startup_latch = new CountDownLatch(1); + Runnable await = () -> { + try { + startup_latch.countDown(); + continue_latch.await(); + } catch (Exception e) { + throw new Error("Failed to await latch", e); + } + }; + Thread thr = new Thread(() -> { await.run(); testObj.run(); }); + thr.start(); + + // Wait until the other thread is started. + startup_latch.await(); + + // Do any final setup. + preTest.accept(testObj); + + // Setup suspension method on the thread. + su.setup(thr); + + // Let the other thread go. + continue_latch.countDown(); + + // Wait for the other thread to hit the breakpoint/watchpoint/whatever and suspend itself + // (without re-entering java) + su.waitForSuspend(thr); + + // Cleanup the breakpoint/watchpoint/etc. + su.cleanup(thr); + + try { + // Pop the frame. + popFrame(thr); + } catch (Exception e) { + System.out.println("Failed to pop frame due to " + e); + SafePrintStackTrace(e.getStackTrace()); + } + + // Start the other thread going again. + Suspension.resume(thr); + + // Wait for the other thread to finish. + thr.join(); + + // See how many times calledFunction was called. + System.out.println("result is " + testObj + " base-call count: " + testObj.getBaseCallCount()); + } + + public static abstract class AbstractTestObject implements TestRunnable { + public int callerCnt; + + public AbstractTestObject() { + callerCnt = 0; + } + + public int getBaseCallCount() { + return callerCnt; + } + + public void run() { + callerCnt++; + // This function should be re-executed by the popFrame. + calledFunction(); + } + + public Method getCalledMethod() throws Exception { + return this.getClass().getMethod("calledFunction"); + } + + public abstract void calledFunction(); + } + + public static class RedefineTestObject extends AbstractTestObject implements Runnable { + public static enum RedefineState { ORIGINAL, REDEFINED, }; + /* public static class RedefineTestObject extends AbstractTestObject implements Runnable { + * public static final byte[] CLASS_BYTES; + * public static final byte[] DEX_BYTES; + * static { + * CLASS_BYTES = null; + * DEX_BYTES = null; + * } + * + * public EnumSet<RedefineState> redefine_states; + * public RedefineTestObject() { + * super(); + * redefine_states = EnumSet.noneOf(RedefineState.class); + * } + * public String toString() { + * return "RedefineTestObject { states: " + redefine_states.toString() + * + " current: REDEFINED }"; + * } + * public void calledFunction() { + * redefine_states.add(RedefineState.REDEFINED); // line +0 + * // We will trigger the redefinition using a breakpoint on the next line. + * doNothing(); // line +2 + * } + * } + */ + public static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADUATQoADQAjBwAkCgAlACYJAAwAJwoAJQAoEgAAACwJAAIALQoAJQAuCgAvADAJAAwA" + + "MQkADAAyBwAzBwA0BwA2AQASUmVkZWZpbmVUZXN0T2JqZWN0AQAMSW5uZXJDbGFzc2VzAQANUmVk" + + "ZWZpbmVTdGF0ZQEAC0NMQVNTX0JZVEVTAQACW0IBAAlERVhfQllURVMBAA9yZWRlZmluZV9zdGF0" + + "ZXMBABNMamF2YS91dGlsL0VudW1TZXQ7AQAJU2lnbmF0dXJlAQBETGphdmEvdXRpbC9FbnVtU2V0" + + "PExhcnQvVGVzdDE5NTMkUmVkZWZpbmVUZXN0T2JqZWN0JFJlZGVmaW5lU3RhdGU7PjsBAAY8aW5p" + + "dD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAIdG9TdHJpbmcBABQoKUxqYXZhL2xh" + + "bmcvU3RyaW5nOwEADmNhbGxlZEZ1bmN0aW9uAQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQANVGVz" + + "dDE5NTMuamF2YQwAGQAaAQAtYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmlu" + + "ZVN0YXRlBwA3DAA4ADkMABUAFgwAHQAeAQAQQm9vdHN0cmFwTWV0aG9kcw8GADoIADsMADwAPQwA" + + "PgA/DABAAEEHAEIMAEMAGgwAEgATDAAUABMBAB9hcnQvVGVzdDE5NTMkUmVkZWZpbmVUZXN0T2Jq" + + "ZWN0AQAfYXJ0L1Rlc3QxOTUzJEFic3RyYWN0VGVzdE9iamVjdAEAEkFic3RyYWN0VGVzdE9iamVj" + + "dAEAEmphdmEvbGFuZy9SdW5uYWJsZQEAEWphdmEvdXRpbC9FbnVtU2V0AQAGbm9uZU9mAQAmKExq" + + "YXZhL2xhbmcvQ2xhc3M7KUxqYXZhL3V0aWwvRW51bVNldDsKAEQARQEAM1JlZGVmaW5lVGVzdE9i" + + "amVjdCB7IHN0YXRlczogASBjdXJyZW50OiBSRURFRklORUQgfQEAF21ha2VDb25jYXRXaXRoQ29u" + + "c3RhbnRzAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlSRURFRklO" + + "RUQBAC9MYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmluZVN0YXRlOwEAA2Fk" + + "ZAEAFShMamF2YS9sYW5nL09iamVjdDspWgEADGFydC9UZXN0MTk1MwEACWRvTm90aGluZwcARgwA" + + "PABJAQAkamF2YS9sYW5nL2ludm9rZS9TdHJpbmdDb25jYXRGYWN0b3J5BwBLAQAGTG9va3VwAQCY" + + "KExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5n" + + "O0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xh" + + "bmcvT2JqZWN0OylMamF2YS9sYW5nL2ludm9rZS9DYWxsU2l0ZTsHAEwBACVqYXZhL2xhbmcvaW52" + + "b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwAQAeamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVz" + + "ACEADAANAAEADgADABkAEgATAAAAGQAUABMAAAABABUAFgABABcAAAACABgABAABABkAGgABABsA" + + "AAAuAAIAAQAAAA4qtwABKhICuAADtQAEsQAAAAEAHAAAAA4AAwAAACEABAAiAA0AIwABAB0AHgAB" + + "ABsAAAAlAAEAAQAAAA0qtAAEtgAFugAGAACwAAAAAQAcAAAABgABAAAAJQABAB8AGgABABsAAAAv" + + "AAIAAQAAAA8qtAAEsgAHtgAIV7gACbEAAAABABwAAAAOAAMAAAApAAsAKwAOACwACAAgABoAAQAb" + + "AAAAKQABAAAAAAAJAbMACgGzAAuxAAAAAQAcAAAADgADAAAAGwAEABwACAAdAAMAIQAAAAIAIgAQ" + + "AAAAIgAEAAwALwAPAAkAAgAMABFAGQANAC8ANQQJAEcASgBIABkAKQAAAAgAAQAqAAEAKw=="); + public static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQAaR23N6WpunLRVX+BexSuzzNNiHNOvQpFoBwAAcAAAAHhWNBIAAAAAAAAAAKQGAAAq" + + "AAAAcAAAABEAAAAYAQAABQAAAFwBAAAEAAAAmAEAAAwAAAC4AQAAAQAAABgCAAAwBQAAOAIAACID" + + "AAA5AwAAQwMAAEsDAABPAwAAXAMAAGcDAABqAwAAbgMAAJEDAADCAwAA5QMAAPUDAAAZBAAAOQQA" + + "AFwEAAB7BAAAjgQAAKIEAAC4BAAAzAQAAOcEAAD8BAAAEQUAABwFAAAwBQAATwUAAF4FAABhBQAA" + + "ZAUAAGgFAABsBQAAeQUAAH4FAACGBQAAlgUAAKEFAACnBQAArwUAAMAFAADKBQAA0QUAAAgAAAAJ" + + "AAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAAGwAAABwA" + + "AAAeAAAABgAAAAsAAAAAAAAABwAAAAwAAAAMAwAABwAAAA0AAAAUAwAAGwAAAA4AAAAAAAAAHQAA" + + "AA8AAAAcAwAAAQABABcAAAACABAABAAAAAIAEAAFAAAAAgANACYAAAAAAAMAAgAAAAIAAwABAAAA" + + "AgADAAIAAAACAAMAIgAAAAIAAAAnAAAAAwADACMAAAAMAAMAAgAAAAwAAQAhAAAADAAAACcAAAAN" + + "AAQAIAAAAA0AAgAlAAAADQAAACcAAAACAAAAAQAAAAAAAAAEAwAAGgAAAIwGAABRBgAAAAAAAAQA" + + "AQACAAAA+gIAAB0AAABUMAMAbhALAAAADAAiAQwAcBAGAAEAGgIZAG4gBwAhAG4gBwABABoAAABu" + + "IAcAAQBuEAgAAQAMABEAAAABAAAAAAAAAPQCAAAGAAAAEgBpAAEAaQACAA4AAgABAAEAAADuAgAA" + + "DAAAAHAQAAABABwAAQBxEAoAAAAMAFsQAwAOAAMAAQACAAAA/gIAAAsAAABUIAMAYgEAAG4gCQAQ" + + "AHEABQAAAA4AIQAOPIcAGwAOPC0AJQAOACkADnk8AAEAAAAKAAAAAQAAAAsAAAABAAAACAAAAAEA" + + "AAAJABUgY3VycmVudDogUkVERUZJTkVEIH0ACDxjbGluaXQ+AAY8aW5pdD4AAj47AAtDTEFTU19C" + + "WVRFUwAJREVYX0JZVEVTAAFMAAJMTAAhTGFydC9UZXN0MTk1MyRBYnN0cmFjdFRlc3RPYmplY3Q7" + + "AC9MYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmluZVN0YXRlOwAhTGFydC9U" + + "ZXN0MTk1MyRSZWRlZmluZVRlc3RPYmplY3Q7AA5MYXJ0L1Rlc3QxOTUzOwAiTGRhbHZpay9hbm5v" + + "dGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ACFM" + + "ZGFsdmlrL2Fubm90YXRpb24vTWVtYmVyQ2xhc3NlczsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWdu" + + "YXR1cmU7ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5n" + + "L1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7" + + "ABNMamF2YS91dGlsL0VudW1TZXQ7ABNMamF2YS91dGlsL0VudW1TZXQ8AAlSRURFRklORUQAElJl" + + "ZGVmaW5lVGVzdE9iamVjdAAdUmVkZWZpbmVUZXN0T2JqZWN0IHsgc3RhdGVzOiAADVRlc3QxOTUz" + + "LmphdmEAAVYAAVoAAlpMAAJbQgALYWNjZXNzRmxhZ3MAA2FkZAAGYXBwZW5kAA5jYWxsZWRGdW5j" + + "dGlvbgAJZG9Ob3RoaW5nAARuYW1lAAZub25lT2YAD3JlZGVmaW5lX3N0YXRlcwAIdG9TdHJpbmcA" + + "BXZhbHVlAFt+fkQ4eyJtaW4tYXBpIjoxLCJzaGEtMSI6IjUyNzNjM2RmZWUxMDQ2NzIwYWY0MjVm" + + "YTg1NTMxNmM5OWM4NmM4ZDIiLCJ2ZXJzaW9uIjoiMS4zLjE4LWRldiJ9AAIHASgcAxcWFwkXAwIE" + + "ASgYAwIFAh8ECSQXGAIGASgcARgBAgECAgEZARkDAQGIgASEBQGBgASgBQMByAUBAbgEAAAAAAAB" + + "AAAALgYAAAMAAAA6BgAAQAYAAEkGAAB8BgAAAQAAAAAAAAAAAAAAAwAAAHQGAAAQAAAAAAAAAAEA" + + "AAAAAAAAAQAAACoAAABwAAAAAgAAABEAAAAYAQAAAwAAAAUAAABcAQAABAAAAAQAAACYAQAABQAA" + + "AAwAAAC4AQAABgAAAAEAAAAYAgAAASAAAAQAAAA4AgAAAyAAAAQAAADuAgAAARAAAAQAAAAEAwAA" + + "AiAAACoAAAAiAwAABCAAAAQAAAAuBgAAACAAAAEAAABRBgAAAxAAAAMAAABwBgAABiAAAAEAAACM" + + "BgAAABAAAAEAAACkBgAA"); + + public EnumSet<RedefineState> redefine_states; + public RedefineTestObject() { + super(); + redefine_states = EnumSet.noneOf(RedefineState.class); + } + + public String toString() { + return "RedefineTestObject { states: " + redefine_states.toString() + " current: ORIGINAL }"; + } + + public void calledFunction() { + redefine_states.add(RedefineState.ORIGINAL); // line +0 + // We will trigger the redefinition using a breakpoint on the next line. + doNothing(); // line +2 + } + } + + public static class ClassLoadObject implements TestRunnable { + public int cnt; + public int baseCallCnt; + + public static final String[] CLASS_NAMES = new String[] { + "Lart/Test1953$ClassLoadObject$TC0;", + "Lart/Test1953$ClassLoadObject$TC1;", + "Lart/Test1953$ClassLoadObject$TC2;", + "Lart/Test1953$ClassLoadObject$TC3;", + "Lart/Test1953$ClassLoadObject$TC4;", + "Lart/Test1953$ClassLoadObject$TC5;", + "Lart/Test1953$ClassLoadObject$TC6;", + "Lart/Test1953$ClassLoadObject$TC7;", + "Lart/Test1953$ClassLoadObject$TC8;", + "Lart/Test1953$ClassLoadObject$TC9;", + }; + + private static int curClass = 0; + + private static class TC0 { public static int foo; static { foo = 1; } } + private static class TC1 { public static int foo; static { foo = 2; } } + private static class TC2 { public static int foo; static { foo = 3; } } + private static class TC3 { public static int foo; static { foo = 4; } } + private static class TC4 { public static int foo; static { foo = 5; } } + private static class TC5 { public static int foo; static { foo = 6; } } + private static class TC6 { public static int foo; static { foo = 7; } } + private static class TC7 { public static int foo; static { foo = 8; } } + private static class TC8 { public static int foo; static { foo = 9; } } + private static class TC9 { public static int foo; static { foo = 10; } } + + public ClassLoadObject() { + super(); + cnt = 0; + baseCallCnt = 0; + } + + public int getBaseCallCount() { + return baseCallCnt; + } + + public void run() { + baseCallCnt++; + if (curClass == 0) { + $noprecompile$calledFunction0(); + } else if (curClass == 1) { + $noprecompile$calledFunction1(); + } else if (curClass == 2) { + $noprecompile$calledFunction2(); + } else if (curClass == 3) { + $noprecompile$calledFunction3(); + } else if (curClass == 4) { + $noprecompile$calledFunction4(); + } else if (curClass == 5) { + $noprecompile$calledFunction5(); + } else if (curClass == 6) { + $noprecompile$calledFunction6(); + } else if (curClass == 7) { + $noprecompile$calledFunction7(); + } else if (curClass == 8) { + $noprecompile$calledFunction8(); + } else if (curClass == 9) { + $noprecompile$calledFunction9(); + } + curClass++; + } + + public Method getCalledMethod() throws Exception { + return this.getClass().getMethod("jnoprecompile$calledFunction" + curClass); + } + + // Give these all a tag to prevent 1954 from compiling them (and loading the class as a + // consequence). + public void $noprecompile$calledFunction0() { + cnt++; + System.out.println("TC0.foo == " + TC0.foo); + } + + public void $noprecompile$calledFunction1() { + cnt++; + System.out.println("TC1.foo == " + TC1.foo); + } + + public void $noprecompile$calledFunction2() { + cnt++; + System.out.println("TC2.foo == " + TC2.foo); + } + + public void $noprecompile$calledFunction3() { + cnt++; + System.out.println("TC3.foo == " + TC3.foo); + } + + public void $noprecompile$calledFunction4() { + cnt++; + System.out.println("TC4.foo == " + TC4.foo); + } + + public void $noprecompile$calledFunction5() { + cnt++; + System.out.println("TC5.foo == " + TC5.foo); + } + + public void $noprecompile$calledFunction6() { + cnt++; + System.out.println("TC6.foo == " + TC6.foo); + } + + public void $noprecompile$calledFunction7() { + cnt++; + System.out.println("TC7.foo == " + TC7.foo); + } + + public void $noprecompile$calledFunction8() { + cnt++; + System.out.println("TC8.foo == " + TC8.foo); + } + + public void $noprecompile$calledFunction9() { + cnt++; + System.out.println("TC9.foo == " + TC9.foo); + } + + public String toString() { + return "ClassLoadObject { cnt: " + cnt + ", curClass: " + curClass + "}"; + } + } + + public static class FieldBasedTestObject extends AbstractTestObject implements Runnable { + public int cnt; + public int TARGET_FIELD; + public FieldBasedTestObject() { + super(); + cnt = 0; + TARGET_FIELD = 0; + } + + public void calledFunction() { + cnt++; + // We put a watchpoint here and PopFrame when we are at it. + TARGET_FIELD += 10; + if (cnt == 1) { System.out.println("FAILED: No pop on first call!"); } + } + + public String toString() { + return "FieldBasedTestObject { cnt: " + cnt + ", TARGET_FIELD: " + TARGET_FIELD + " }"; + } + } + + public static class StandardTestObject extends AbstractTestObject implements Runnable { + public int cnt; + public final boolean check; + + public StandardTestObject(boolean check) { + super(); + cnt = 0; + this.check = check; + } + + public StandardTestObject() { + this(true); + } + + public void calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and PopFrame when we are at it. + doNothing(); // line +2 + if (check && cnt == 1) { System.out.println("FAILED: No pop on first call!"); } + } + + public String toString() { + return "StandardTestObject { cnt: " + cnt + " }"; + } + } + + public static class SynchronizedFunctionTestObject extends AbstractTestObject implements Runnable { + public int cnt; + + public SynchronizedFunctionTestObject() { + super(); + cnt = 0; + } + + public synchronized void calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and PopFrame when we are at it. + doNothing(); // line +2 + } + + public String toString() { + return "SynchronizedFunctionTestObject { cnt: " + cnt + " }"; + } + } + public static class SynchronizedTestObject extends AbstractTestObject implements Runnable { + public int cnt; + public final Object lock; + + public SynchronizedTestObject() { + super(); + cnt = 0; + lock = new Object(); + } + + public void calledFunction() { + synchronized (lock) { // line +0 + cnt++; // line +1 + // We put a breakpoint here and PopFrame when we are at it. + doNothing(); // line +3 + } + } + + public String toString() { + return "SynchronizedTestObject { cnt: " + cnt + " }"; + } + } + + public static class ExceptionCatchTestObject extends AbstractTestObject implements Runnable { + public static class TestError extends Error {} + + public int cnt; + public ExceptionCatchTestObject() { + super(); + cnt = 0; + } + + public void calledFunction() { + cnt++; + try { + doThrow(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in called function."); + } + } + + public void doThrow() { + throw new TestError(); + } + + public String toString() { + return "ExceptionCatchTestObject { cnt: " + cnt + " }"; + } + } + + public static class ExceptionThrowFarTestObject implements TestRunnable { + public static class TestError extends Error {} + + public int cnt; + public int baseCallCnt; + public final boolean catchInCalled; + public ExceptionThrowFarTestObject(boolean catchInCalled) { + super(); + cnt = 0; + baseCallCnt = 0; + this.catchInCalled = catchInCalled; + } + + public int getBaseCallCount() { + return baseCallCnt; + } + + public void run() { + baseCallCnt++; + try { + callingFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + } + + public void callingFunction() { + calledFunction(); + } + public void calledFunction() { + cnt++; + if (catchInCalled) { + try { + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + } + } else { + throw new TestError(); // We put a watch here. + } + } + + public Method getCallingMethod() throws Exception { + return this.getClass().getMethod("callingFunction"); + } + + public Method getCalledMethod() throws Exception { + return this.getClass().getMethod("calledFunction"); + } + + public String toString() { + return "ExceptionThrowFarTestObject { cnt: " + cnt + " }"; + } + } + + public static class ExceptionOnceObject extends AbstractTestObject { + public static final class TestError extends Error {} + public int cnt; + public final boolean throwInSub; + public ExceptionOnceObject(boolean throwInSub) { + super(); + cnt = 0; + this.throwInSub = throwInSub; + } + + public void calledFunction() { + cnt++; + if (cnt == 1) { + if (throwInSub) { + doThrow(); + } else { + throw new TestError(); + } + } + } + + public void doThrow() { + throw new TestError(); + } + + public String toString() { + return "ExceptionOnceObject { cnt: " + cnt + ", throwInSub: " + throwInSub + " }"; + } + } + + public static class ExceptionThrowTestObject implements TestRunnable { + public static class TestError extends Error {} + + public int cnt; + public int baseCallCnt; + public final boolean catchInCalled; + public ExceptionThrowTestObject(boolean catchInCalled) { + super(); + cnt = 0; + baseCallCnt = 0; + this.catchInCalled = catchInCalled; + } + + public int getBaseCallCount() { + return baseCallCnt; + } + + public void run() { + baseCallCnt++; + try { + calledFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + } + + public void calledFunction() { + cnt++; + if (catchInCalled) { + try { + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + } + } else { + throw new TestError(); // We put a watch here. + } + } + + public Method getCalledMethod() throws Exception { + return this.getClass().getMethod("calledFunction"); + } + + public String toString() { + return "ExceptionThrowTestObject { cnt: " + cnt + " }"; + } + } + + public static class NativeCalledObject extends AbstractTestObject { + public int cnt = 0; + + public native void calledFunction(); + + public String toString() { + return "NativeCalledObject { cnt: " + cnt + " }"; + } + } + + public static class NativeCallerObject implements TestRunnable { + public int baseCnt = 0; + public int cnt = 0; + + public int getBaseCallCount() { + return baseCnt; + } + + public native void run(); + + public void calledFunction() { + cnt++; + // We will stop using a MethodExit event. + } + + public Method getCalledMethod() throws Exception { + return this.getClass().getMethod("calledFunction"); + } + + public String toString() { + return "NativeCallerObject { cnt: " + cnt + " }"; + } + } + public static class SuspendSuddenlyObject extends AbstractTestObject { + public volatile boolean stop_spinning = false; + public volatile boolean is_spinning = false; + public int cnt = 0; + + public void calledFunction() { + cnt++; + while (!stop_spinning) { + is_spinning = true; + } + } + + public String toString() { + return "SuspendSuddenlyObject { cnt: " + cnt + " }"; + } + } + + public static void run(boolean canRunClassLoadTests) throws Exception { + new Test1953(canRunClassLoadTests, (x)-> {}).runTests(); + } + + // This entrypoint is used by CTS only. */ + public static void run() throws Exception { + /* TODO: Due to the way that CTS tests are verified we cannot run class-load-tests since the + * verifier will be delayed until runtime and then load the classes all at once. This + * makes the test impossible to run. + */ + run(/*canRunClassLoadTests*/ false); + } + + public Test1953(boolean canRunClassLoadTests, Consumer<TestRunnable> preTest) { + this.canRunClassLoadTests = canRunClassLoadTests; + this.preTest = preTest; + } + + private Consumer<TestRunnable> preTest; + + public void runTests() throws Exception { + setupTest(); + + final Method calledFunction = StandardTestObject.class.getDeclaredMethod("calledFunction"); + final Method doNothingMethod = Test1953.class.getDeclaredMethod("doNothing"); + // Add a breakpoint on the second line after the start of the function + final int line = Breakpoint.locationToLine(calledFunction, 0) + 2; + final long loc = Breakpoint.lineToLocation(calledFunction, line); + System.out.println("Test stopped using breakpoint"); + runTestOn(new StandardTestObject(), + (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr), + Test1953::clearSuspendBreakpointFor); + + final Method syncFunctionCalledFunction = + SynchronizedFunctionTestObject.class.getDeclaredMethod("calledFunction"); + // Add a breakpoint on the second line after the start of the function + // Annoyingly r8 generally has the first instruction (a monitor enter) not be marked as being + // on any line but javac has it marked as being on the first line of the function. Just use the + // second entry on the line-number table to get the breakpoint. This should be good for both. + final long syncFunctionLoc = + Breakpoint.getLineNumberTable(syncFunctionCalledFunction)[1].location; + System.out.println("Test stopped using breakpoint with declared synchronized function"); + runTestOn(new SynchronizedFunctionTestObject(), + (thr) -> setupSuspendBreakpointFor(syncFunctionCalledFunction, syncFunctionLoc, thr), + Test1953::clearSuspendBreakpointFor); + + final Method syncCalledFunction = + SynchronizedTestObject.class.getDeclaredMethod("calledFunction"); + // Add a breakpoint on the second line after the start of the function + final int syncLine = Breakpoint.locationToLine(syncCalledFunction, 0) + 3; + final long syncLoc = Breakpoint.lineToLocation(syncCalledFunction, syncLine); + System.out.println("Test stopped using breakpoint with synchronized block"); + runTestOn(new SynchronizedTestObject(), + (thr) -> setupSuspendBreakpointFor(syncCalledFunction, syncLoc, thr), + Test1953::clearSuspendBreakpointFor); + + System.out.println("Test stopped on single step"); + runTestOn(new StandardTestObject(), + (thr) -> setupSuspendSingleStepAt(calledFunction, loc, thr), + Test1953::clearSuspendSingleStepFor); + + final Field target_field = FieldBasedTestObject.class.getDeclaredField("TARGET_FIELD"); + System.out.println("Test stopped on field access"); + runTestOn(new FieldBasedTestObject(), + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, true, thr), + Test1953::clearFieldSuspendFor); + + System.out.println("Test stopped on field modification"); + runTestOn(new FieldBasedTestObject(), + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, false, thr), + Test1953::clearFieldSuspendFor); + + System.out.println("Test stopped during Method Exit of doNothing"); + runTestOn(new StandardTestObject(false), + (thr) -> setupSuspendMethodEvent(doNothingMethod, /*enter*/ false, thr), + Test1953::clearSuspendMethodEvent); + + // NB We need another test to make sure the MethodEntered event is triggered twice. + System.out.println("Test stopped during Method Enter of doNothing"); + runTestOn(new StandardTestObject(false), + (thr) -> setupSuspendMethodEvent(doNothingMethod, /*enter*/ true, thr), + Test1953::clearSuspendMethodEvent); + + System.out.println("Test stopped during Method Exit of calledFunction"); + runTestOn(new StandardTestObject(false), + (thr) -> setupSuspendMethodEvent(calledFunction, /*enter*/ false, thr), + Test1953::clearSuspendMethodEvent); + + System.out.println("Test stopped during Method Enter of calledFunction"); + runTestOn(new StandardTestObject(false), + (thr) -> setupSuspendMethodEvent(calledFunction, /*enter*/ true, thr), + Test1953::clearSuspendMethodEvent); + + final Method exceptionOnceCalledMethod = + ExceptionOnceObject.class.getDeclaredMethod("calledFunction"); + System.out.println("Test stopped during Method Exit due to exception thrown in same function"); + runTestOn(new ExceptionOnceObject(/*throwInSub*/ false), + (thr) -> setupSuspendMethodEvent(exceptionOnceCalledMethod, /*enter*/ false, thr), + Test1953::clearSuspendMethodEvent); + + System.out.println("Test stopped during Method Exit due to exception thrown in subroutine"); + runTestOn(new ExceptionOnceObject(/*throwInSub*/ true), + (thr) -> setupSuspendMethodEvent(exceptionOnceCalledMethod, /*enter*/ false, thr), + Test1953::clearSuspendMethodEvent); + + System.out.println("Test stopped during notifyFramePop without exception on pop of calledFunction"); + runTestOn(new StandardTestObject(false), + (thr) -> setupSuspendPopFrameEvent(1, doNothingMethod, thr), + Test1953::clearSuspendPopFrameEvent); + + System.out.println("Test stopped during notifyFramePop without exception on pop of doNothing"); + runTestOn(new StandardTestObject(false), + (thr) -> setupSuspendPopFrameEvent(0, doNothingMethod, thr), + Test1953::clearSuspendPopFrameEvent); + + final Method exceptionThrowCalledMethod = + ExceptionThrowTestObject.class.getDeclaredMethod("calledFunction"); + System.out.println("Test stopped during notifyFramePop with exception on pop of calledFunction"); + runTestOn(new ExceptionThrowTestObject(false), + (thr) -> setupSuspendPopFrameEvent(0, exceptionThrowCalledMethod, thr), + Test1953::clearSuspendPopFrameEvent); + + final Method exceptionCatchThrowMethod = + ExceptionCatchTestObject.class.getDeclaredMethod("doThrow"); + System.out.println("Test stopped during notifyFramePop with exception on pop of doThrow"); + runTestOn(new ExceptionCatchTestObject(), + (thr) -> setupSuspendPopFrameEvent(0, exceptionCatchThrowMethod, thr), + Test1953::clearSuspendPopFrameEvent); + + System.out.println("Test stopped during ExceptionCatch event of calledFunction " + + "(catch in called function, throw in called function)"); + runTestOn(new ExceptionThrowTestObject(true), + (thr) -> setupSuspendExceptionEvent(exceptionThrowCalledMethod, /*catch*/ true, thr), + Test1953::clearSuspendExceptionEvent); + + final Method exceptionCatchCalledMethod = + ExceptionCatchTestObject.class.getDeclaredMethod("calledFunction"); + System.out.println("Test stopped during ExceptionCatch event of calledFunction " + + "(catch in called function, throw in subroutine)"); + runTestOn(new ExceptionCatchTestObject(), + (thr) -> setupSuspendExceptionEvent(exceptionCatchCalledMethod, /*catch*/ true, thr), + Test1953::clearSuspendExceptionEvent); + + System.out.println("Test stopped during Exception event of calledFunction " + + "(catch in calling function)"); + runTestOn(new ExceptionThrowTestObject(false), + (thr) -> setupSuspendExceptionEvent(exceptionThrowCalledMethod, /*catch*/ false, thr), + Test1953::clearSuspendExceptionEvent); + + System.out.println("Test stopped during Exception event of calledFunction " + + "(catch in called function)"); + runTestOn(new ExceptionThrowTestObject(true), + (thr) -> setupSuspendExceptionEvent(exceptionThrowCalledMethod, /*catch*/ false, thr), + Test1953::clearSuspendExceptionEvent); + + final Method exceptionThrowFarCalledMethod = + ExceptionThrowFarTestObject.class.getDeclaredMethod("calledFunction"); + System.out.println("Test stopped during Exception event of calledFunction " + + "(catch in parent of calling function)"); + runTestOn(new ExceptionThrowFarTestObject(false), + (thr) -> setupSuspendExceptionEvent(exceptionThrowFarCalledMethod, /*catch*/ false, thr), + Test1953::clearSuspendExceptionEvent); + + System.out.println("Test stopped during Exception event of calledFunction " + + "(catch in called function)"); + runTestOn(new ExceptionThrowFarTestObject(true), + (thr) -> setupSuspendExceptionEvent(exceptionThrowFarCalledMethod, /*catch*/ false, thr), + Test1953::clearSuspendExceptionEvent); + + // These tests are disabled for either the RI (b/116003018) or for jvmti-stress. For the + // later it is due to the additional agent causing classes to be loaded earlier as it forces + // deeper verification during class redefinition, causing failures. + // NB the agent is prevented from popping frames in either of these events in ART. See + // b/117615146 for more information about this restriction. + if (canRunClassLoadTests && CanRunClassLoadingTests()) { + // This test doesn't work on RI since the RI disallows use of PopFrame during a ClassLoad + // event. See b/116003018 for more information. + System.out.println("Test stopped during a ClassLoad event."); + runTestOn(new ClassLoadObject(), + (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_LOAD, ClassLoadObject.CLASS_NAMES, thr), + Test1953::clearSuspendClassEvent); + + // The RI handles a PopFrame during a ClassPrepare event incorrectly. See b/116003018 for + // more information. + System.out.println("Test stopped during a ClassPrepare event."); + runTestOn(new ClassLoadObject(), + (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_PREPARE, + ClassLoadObject.CLASS_NAMES, + thr), + Test1953::clearSuspendClassEvent); + } + System.out.println("Test stopped during random Suspend."); + final SuspendSuddenlyObject sso = new SuspendSuddenlyObject(); + runTestOn( + sso, + new TestSuspender() { + public void setup(Thread thr) { } + public void waitForSuspend(Thread thr) { + while (!sso.is_spinning) {} + Suspension.suspend(thr); + } + public void cleanup(Thread thr) { + sso.stop_spinning = true; + } + }); + + final Method redefineCalledFunction = + RedefineTestObject.class.getDeclaredMethod("calledFunction"); + final int redefLine = Breakpoint.locationToLine(redefineCalledFunction, 0) + 2; + final long redefLoc = Breakpoint.lineToLocation(redefineCalledFunction, redefLine); + System.out.println("Test redefining frame being popped."); + runTestOn(new RedefineTestObject(), + (thr) -> setupSuspendBreakpointFor(redefineCalledFunction, redefLoc, thr), + (thr) -> { + clearSuspendBreakpointFor(thr); + Redefinition.doCommonClassRedefinition(RedefineTestObject.class, + RedefineTestObject.CLASS_BYTES, + RedefineTestObject.DEX_BYTES); + }); + + System.out.println("Test stopped during a native method fails"); + runTestOn(new NativeCalledObject(), + Test1953::setupWaitForNativeCall, + Test1953::clearWaitForNativeCall); + + System.out.println("Test stopped in a method called by native fails"); + final Method nativeCallerMethod = NativeCallerObject.class.getDeclaredMethod("calledFunction"); + runTestOn(new NativeCallerObject(), + (thr) -> setupSuspendMethodEvent(nativeCallerMethod, /*enter*/ false, thr), + Test1953::clearSuspendMethodEvent); + } + + // Volatile is to prevent any future optimizations that could invalidate this test by doing + // constant propagation and eliminating the failing paths before the verifier is able to load the + // class. + static volatile boolean ranClassLoadTest = false; + static boolean classesPreverified = false; + private static final class RCLT0 { public void foo() {} } + private static final class RCLT1 { public void foo() {} } + // If classes are not preverified for some reason (interp-ac, no-image, etc) the verifier will + // actually load classes as it runs. This means that we cannot use the class-load tests as they + // are written. TODO Support this. + public boolean CanRunClassLoadingTests() { + if (ranClassLoadTest) { + return classesPreverified; + } + if (!ranClassLoadTest) { + // Only this will ever be executed. + new RCLT0().foo(); + } else { + // This will never be executed. If classes are not preverified the verifier will load RCLT1 + // when the enclosing method is run. This behavior makes the class-load/prepare test cases + // impossible to successfully run (they will deadlock). + new RCLT1().foo(); + System.out.println("FAILURE: UNREACHABLE Location!"); + } + classesPreverified = !isClassLoaded("Lart/Test1953$RCLT1;"); + ranClassLoadTest = true; + return classesPreverified; + } + + public static native boolean isClassLoaded(String name); + + public static native void setupTest(); + public static native void popFrame(Thread thr); + + public static native void setupSuspendBreakpointFor(Executable meth, long loc, Thread thr); + public static native void clearSuspendBreakpointFor(Thread thr); + + public static native void setupSuspendSingleStepAt(Executable meth, long loc, Thread thr); + public static native void clearSuspendSingleStepFor(Thread thr); + + public static native void setupFieldSuspendFor(Class klass, Field f, boolean access, Thread thr); + public static native void clearFieldSuspendFor(Thread thr); + + public static native void setupSuspendMethodEvent(Executable meth, boolean enter, Thread thr); + public static native void clearSuspendMethodEvent(Thread thr); + + public static native void setupSuspendExceptionEvent( + Executable meth, boolean is_catch, Thread thr); + public static native void clearSuspendExceptionEvent(Thread thr); + + public static native void setupSuspendPopFrameEvent( + int offset, Executable breakpointFunction, Thread thr); + public static native void clearSuspendPopFrameEvent(Thread thr); + + public static final int EVENT_TYPE_CLASS_LOAD = 55; + public static final int EVENT_TYPE_CLASS_PREPARE = 56; + public static native void setupSuspendClassEvent( + int eventType, String[] interestingNames, Thread thr); + public static native void clearSuspendClassEvent(Thread thr); + + public static native void setupWaitForNativeCall(Thread thr); + public static native void clearWaitForNativeCall(Thread thr); + + public static native void waitForSuspendHit(Thread thr); +} diff --git a/test/1954-pop-frame-jit/check b/test/1954-pop-frame-jit/check new file mode 100755 index 0000000000..10b87cc286 --- /dev/null +++ b/test/1954-pop-frame-jit/check @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (C) 2018 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. + +# The RI has restrictions and bugs around some PopFrame behavior that ART lacks. +# See b/116003018. Some configurations cannot handle the class load events in +# quite the right way so they are disabled there too. +./default-check "$@" || \ + (patch -p0 expected.txt < jvm-expected.patch >/dev/null && ./default-check "$@") diff --git a/test/1954-pop-frame-jit/expected.txt b/test/1954-pop-frame-jit/expected.txt new file mode 100644 index 0000000000..a20a045ffa --- /dev/null +++ b/test/1954-pop-frame-jit/expected.txt @@ -0,0 +1,118 @@ +Test stopped using breakpoint +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with declared synchronized function +Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with synchronized block +Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedTestObject { cnt: 2 } base-call count: 1 +Test stopped on single step +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped on field access +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped on field modification +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped during Method Exit of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Enter of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during Method Enter of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit due to exception thrown in same function +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1 +Test stopped during Method Exit due to exception thrown in subroutine +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of calledFunction +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of doThrow +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in calling function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in parent of calling function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught! +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function. +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during a ClassLoad event. +Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +TC0.foo == 1 +result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +Test stopped during a ClassPrepare event. +Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +TC1.foo == 2 +result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 +Test stopped during random Suspend. +Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0 +result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1 +Test redefining frame being popped. +Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0 +result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1 +Test stopped during a native method fails +Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCalledObject { cnt: 1 } base-call count: 1 +Test stopped in a method called by native fails +Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCallerObject { cnt: 1 } base-call count: 1 diff --git a/test/1954-pop-frame-jit/info.txt b/test/1954-pop-frame-jit/info.txt new file mode 100644 index 0000000000..b5eb5464b7 --- /dev/null +++ b/test/1954-pop-frame-jit/info.txt @@ -0,0 +1,7 @@ +Test basic JVMTI breakpoint functionality. + +This test places a breakpoint on the first instruction of a number of functions +that are entered in every way possible for the given class of method. + +It also tests that breakpoints don't interfere with each other by having +multiple breakpoints be set at once. diff --git a/test/1954-pop-frame-jit/jvm-expected.patch b/test/1954-pop-frame-jit/jvm-expected.patch new file mode 100644 index 0000000000..718f8ad839 --- /dev/null +++ b/test/1954-pop-frame-jit/jvm-expected.patch @@ -0,0 +1,21 @@ +75,94d74 +< Test stopped during a ClassLoad event. +< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +< art.Test1953.popFrame(Native Method) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTests(Test1953.java) +< <Additional frames hidden> +< TC0.foo == 1 +< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +< Test stopped during a ClassPrepare event. +< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +< art.Test1953.popFrame(Native Method) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTests(Test1953.java) +< <Additional frames hidden> +< TC1.foo == 2 +< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 diff --git a/test/1954-pop-frame-jit/run b/test/1954-pop-frame-jit/run new file mode 100755 index 0000000000..d16d4e6091 --- /dev/null +++ b/test/1954-pop-frame-jit/run @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# On RI we need to turn class-load tests off since those events are buggy around +# pop-frame (see b/116003018). +ARGS="" +if [[ "$TEST_RUNTIME" == "jvm" ]]; then + ARGS="--args DISABLE_CLASS_LOAD_TESTS" +fi + +./default-run "$@" --jvmti $ARGS diff --git a/test/1954-pop-frame-jit/src/Main.java b/test/1954-pop-frame-jit/src/Main.java new file mode 100644 index 0000000000..12defcda9a --- /dev/null +++ b/test/1954-pop-frame-jit/src/Main.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.time.Duration; + +import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Stack; +import java.util.Vector; + +import java.util.function.Supplier; + +import art.*; + +public class Main extends Test1953 { + public Main(boolean run_class_load_tests) { + super(run_class_load_tests, (testObj) -> { + try { + // Make sure everything is jitted in the method. We do this before calling setup since the + // suspend setup might make it impossible to jit the methods (by setting breakpoints or + // something). + for (Method m : testObj.getClass().getMethods()) { + if ((m.getModifiers() & Modifier.NATIVE) == 0 && + !m.getName().startsWith("$noprecompile$")) { + ensureMethodJitCompiled(m); + } + } + } catch (Exception e) {} + }); + } + + public static void main(String[] args) throws Exception { + new Main(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")).runTests(); + } + + public static native void ensureMethodJitCompiled(Method meth); +} diff --git a/test/1954-pop-frame-jit/src/art/Breakpoint.java b/test/1954-pop-frame-jit/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1954-pop-frame-jit/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1954-pop-frame-jit/src/art/Redefinition.java b/test/1954-pop-frame-jit/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/1954-pop-frame-jit/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/1954-pop-frame-jit/src/art/StackTrace.java b/test/1954-pop-frame-jit/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1954-pop-frame-jit/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1954-pop-frame-jit/src/art/Suspension.java b/test/1954-pop-frame-jit/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1954-pop-frame-jit/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1954-pop-frame-jit/src/art/Test1953.java b/test/1954-pop-frame-jit/src/art/Test1953.java new file mode 120000 index 0000000000..f28143484a --- /dev/null +++ b/test/1954-pop-frame-jit/src/art/Test1953.java @@ -0,0 +1 @@ +../../../1953-pop-frame/src/art/Test1953.java
\ No newline at end of file diff --git a/test/1955-pop-frame-jit-called/check b/test/1955-pop-frame-jit-called/check new file mode 100755 index 0000000000..10b87cc286 --- /dev/null +++ b/test/1955-pop-frame-jit-called/check @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (C) 2018 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. + +# The RI has restrictions and bugs around some PopFrame behavior that ART lacks. +# See b/116003018. Some configurations cannot handle the class load events in +# quite the right way so they are disabled there too. +./default-check "$@" || \ + (patch -p0 expected.txt < jvm-expected.patch >/dev/null && ./default-check "$@") diff --git a/test/1955-pop-frame-jit-called/expected.txt b/test/1955-pop-frame-jit-called/expected.txt new file mode 100644 index 0000000000..a20a045ffa --- /dev/null +++ b/test/1955-pop-frame-jit-called/expected.txt @@ -0,0 +1,118 @@ +Test stopped using breakpoint +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with declared synchronized function +Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with synchronized block +Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedTestObject { cnt: 2 } base-call count: 1 +Test stopped on single step +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped on field access +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped on field modification +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped during Method Exit of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Enter of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during Method Enter of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit due to exception thrown in same function +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1 +Test stopped during Method Exit due to exception thrown in subroutine +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of calledFunction +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of doThrow +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in calling function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in parent of calling function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught! +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function. +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during a ClassLoad event. +Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +TC0.foo == 1 +result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +Test stopped during a ClassPrepare event. +Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +TC1.foo == 2 +result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 +Test stopped during random Suspend. +Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0 +result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1 +Test redefining frame being popped. +Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0 +result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1 +Test stopped during a native method fails +Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCalledObject { cnt: 1 } base-call count: 1 +Test stopped in a method called by native fails +Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCallerObject { cnt: 1 } base-call count: 1 diff --git a/test/1955-pop-frame-jit-called/info.txt b/test/1955-pop-frame-jit-called/info.txt new file mode 100644 index 0000000000..b5eb5464b7 --- /dev/null +++ b/test/1955-pop-frame-jit-called/info.txt @@ -0,0 +1,7 @@ +Test basic JVMTI breakpoint functionality. + +This test places a breakpoint on the first instruction of a number of functions +that are entered in every way possible for the given class of method. + +It also tests that breakpoints don't interfere with each other by having +multiple breakpoints be set at once. diff --git a/test/1955-pop-frame-jit-called/jvm-expected.patch b/test/1955-pop-frame-jit-called/jvm-expected.patch new file mode 100644 index 0000000000..718f8ad839 --- /dev/null +++ b/test/1955-pop-frame-jit-called/jvm-expected.patch @@ -0,0 +1,21 @@ +75,94d74 +< Test stopped during a ClassLoad event. +< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +< art.Test1953.popFrame(Native Method) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTests(Test1953.java) +< <Additional frames hidden> +< TC0.foo == 1 +< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +< Test stopped during a ClassPrepare event. +< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +< art.Test1953.popFrame(Native Method) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTests(Test1953.java) +< <Additional frames hidden> +< TC1.foo == 2 +< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 diff --git a/test/1955-pop-frame-jit-called/run b/test/1955-pop-frame-jit-called/run new file mode 100755 index 0000000000..2984461057 --- /dev/null +++ b/test/1955-pop-frame-jit-called/run @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# On RI we need to turn class-load tests off since those events are buggy around +# pop-frame (see b/116003018). +ARGS="" +if [[ "$TEST_RUNTIME" == "jvm" ]]; then + ARGS="--args DISABLE_CLASS_LOAD_TESTS" +fi + +# The jitthreshold prevents the jit from compiling anything except those which +# we explicitly request. +./default-run "$@" --android-runtime-option -Xjitthreshold:1000 --jvmti $ARGS diff --git a/test/1955-pop-frame-jit-called/src/Main.java b/test/1955-pop-frame-jit-called/src/Main.java new file mode 100644 index 0000000000..30a42ea15a --- /dev/null +++ b/test/1955-pop-frame-jit-called/src/Main.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.time.Duration; + +import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Stack; +import java.util.Vector; + +import java.util.function.Supplier; + +import art.*; + +public class Main extends Test1953 { + public Main(boolean run_class_load_tests) { + super(run_class_load_tests, (testObj) -> { + try { + // Make sure the called method is jitted + ensureMethodJitCompiled(testObj.getCalledMethod()); + } catch (Exception e) {} + }); + } + + public static void main(String[] args) throws Exception { + new Main(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")).runTests(); + } + + public static native void ensureMethodJitCompiled(Method meth); +} diff --git a/test/1955-pop-frame-jit-called/src/art/Breakpoint.java b/test/1955-pop-frame-jit-called/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1955-pop-frame-jit-called/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1955-pop-frame-jit-called/src/art/Redefinition.java b/test/1955-pop-frame-jit-called/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/1955-pop-frame-jit-called/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/1955-pop-frame-jit-called/src/art/StackTrace.java b/test/1955-pop-frame-jit-called/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1955-pop-frame-jit-called/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1955-pop-frame-jit-called/src/art/Suspension.java b/test/1955-pop-frame-jit-called/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1955-pop-frame-jit-called/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1955-pop-frame-jit-called/src/art/Test1953.java b/test/1955-pop-frame-jit-called/src/art/Test1953.java new file mode 120000 index 0000000000..f28143484a --- /dev/null +++ b/test/1955-pop-frame-jit-called/src/art/Test1953.java @@ -0,0 +1 @@ +../../../1953-pop-frame/src/art/Test1953.java
\ No newline at end of file diff --git a/test/1956-pop-frame-jit-calling/check b/test/1956-pop-frame-jit-calling/check new file mode 100755 index 0000000000..10b87cc286 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/check @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Copyright (C) 2018 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. + +# The RI has restrictions and bugs around some PopFrame behavior that ART lacks. +# See b/116003018. Some configurations cannot handle the class load events in +# quite the right way so they are disabled there too. +./default-check "$@" || \ + (patch -p0 expected.txt < jvm-expected.patch >/dev/null && ./default-check "$@") diff --git a/test/1956-pop-frame-jit-calling/expected.txt b/test/1956-pop-frame-jit-calling/expected.txt new file mode 100644 index 0000000000..a20a045ffa --- /dev/null +++ b/test/1956-pop-frame-jit-calling/expected.txt @@ -0,0 +1,118 @@ +Test stopped using breakpoint +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with declared synchronized function +Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1 +Test stopped using breakpoint with synchronized block +Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0 +result is SynchronizedTestObject { cnt: 2 } base-call count: 1 +Test stopped on single step +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped on field access +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped on field modification +Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0 +result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1 +Test stopped during Method Exit of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Enter of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during Method Enter of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during Method Exit due to exception thrown in same function +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1 +Test stopped during Method Exit due to exception thrown in subroutine +Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0 +result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of calledFunction +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop without exception on pop of doNothing +Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0 +result is StandardTestObject { cnt: 1 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of calledFunction +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during notifyFramePop with exception on pop of doThrow +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionCatchTestObject$TestError caught in called function. +result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in calling function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError thrown and caught! +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowTestObject$TestError caught in same function. +result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in parent of calling function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught! +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during Exception event of calledFunction (catch in called function) +Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0 +art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function. +result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1 +Test stopped during a ClassLoad event. +Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +TC0.foo == 1 +result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +Test stopped during a ClassPrepare event. +Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +TC1.foo == 2 +result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 +Test stopped during random Suspend. +Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0 +result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1 +Test redefining frame being popped. +Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0 +result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1 +Test stopped during a native method fails +Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCalledObject { cnt: 1 } base-call count: 1 +Test stopped in a method called by native fails +Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0 +Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.Test1953.popFrame(Native Method) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTestOn(Test1953.java) + art.Test1953.runTests(Test1953.java) + <Additional frames hidden> +result is NativeCallerObject { cnt: 1 } base-call count: 1 diff --git a/test/1956-pop-frame-jit-calling/info.txt b/test/1956-pop-frame-jit-calling/info.txt new file mode 100644 index 0000000000..b5eb5464b7 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/info.txt @@ -0,0 +1,7 @@ +Test basic JVMTI breakpoint functionality. + +This test places a breakpoint on the first instruction of a number of functions +that are entered in every way possible for the given class of method. + +It also tests that breakpoints don't interfere with each other by having +multiple breakpoints be set at once. diff --git a/test/1956-pop-frame-jit-calling/jvm-expected.patch b/test/1956-pop-frame-jit-calling/jvm-expected.patch new file mode 100644 index 0000000000..718f8ad839 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/jvm-expected.patch @@ -0,0 +1,21 @@ +75,94d74 +< Test stopped during a ClassLoad event. +< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0 +< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +< art.Test1953.popFrame(Native Method) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTests(Test1953.java) +< <Additional frames hidden> +< TC0.foo == 1 +< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1 +< Test stopped during a ClassPrepare event. +< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0 +< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +< art.Test1953.popFrame(Native Method) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTestOn(Test1953.java) +< art.Test1953.runTests(Test1953.java) +< <Additional frames hidden> +< TC1.foo == 2 +< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1 diff --git a/test/1956-pop-frame-jit-calling/run b/test/1956-pop-frame-jit-calling/run new file mode 100755 index 0000000000..2984461057 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/run @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Copyright 2017 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. + +# On RI we need to turn class-load tests off since those events are buggy around +# pop-frame (see b/116003018). +ARGS="" +if [[ "$TEST_RUNTIME" == "jvm" ]]; then + ARGS="--args DISABLE_CLASS_LOAD_TESTS" +fi + +# The jitthreshold prevents the jit from compiling anything except those which +# we explicitly request. +./default-run "$@" --android-runtime-option -Xjitthreshold:1000 --jvmti $ARGS diff --git a/test/1956-pop-frame-jit-calling/src/Main.java b/test/1956-pop-frame-jit-calling/src/Main.java new file mode 100644 index 0000000000..c44e035a8d --- /dev/null +++ b/test/1956-pop-frame-jit-calling/src/Main.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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. + */ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.time.Duration; + +import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.Stack; +import java.util.Vector; + +import java.util.function.Supplier; + +import art.*; + +public class Main extends Test1953 { + public Main(boolean run_class_load_tests) { + super(run_class_load_tests, (testObj) -> { + try { + // Make sure the calling method is jitted + ensureMethodJitCompiled(testObj.getCallingMethod()); + } catch (Exception e) {} + }); + } + + public static void main(String[] args) throws Exception { + new Main(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")).runTests(); + } + + public static native void ensureMethodJitCompiled(Method meth); +} diff --git a/test/1956-pop-frame-jit-calling/src/art/Breakpoint.java b/test/1956-pop-frame-jit-calling/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1956-pop-frame-jit-calling/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1956-pop-frame-jit-calling/src/art/Redefinition.java b/test/1956-pop-frame-jit-calling/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/1956-pop-frame-jit-calling/src/art/StackTrace.java b/test/1956-pop-frame-jit-calling/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1956-pop-frame-jit-calling/src/art/Suspension.java b/test/1956-pop-frame-jit-calling/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1956-pop-frame-jit-calling/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1956-pop-frame-jit-calling/src/art/Test1953.java b/test/1956-pop-frame-jit-calling/src/art/Test1953.java new file mode 120000 index 0000000000..f28143484a --- /dev/null +++ b/test/1956-pop-frame-jit-calling/src/art/Test1953.java @@ -0,0 +1 @@ +../../../1953-pop-frame/src/art/Test1953.java
\ No newline at end of file diff --git a/test/Android.bp b/test/Android.bp index 8f23058cf7..8c1c1bf32e 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -291,6 +291,7 @@ art_cc_defaults { "1946-list-descriptors/descriptors.cc", "1950-unprepared-transform/unprepared_transform.cc", "1951-monitor-enter-no-suspend/raw_monitor.cc", + "1953-pop-frame/pop_frame.cc", ], // Use NDK-compatible headers for ctstiagent. header_libs: [ @@ -320,6 +321,7 @@ art_cc_defaults { "983-source-transform-verify/source_transform_art.cc", "1940-ddms-ext/ddm_ext.cc", "1944-sudden-exit/sudden_exit.cc", + // "1952-pop-frame-jit/pop_frame.cc", ], static_libs: [ "libz", diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index 4967834490..65127fcab1 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -21,11 +21,13 @@ #include "art_method-inl.h" #include "base/enums.h" +#include "common_throws.h" #include "dex/dex_file-inl.h" #include "instrumentation.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "jit/profiling_info.h" +#include "jni/jni_internal.h" #include "mirror/class-inl.h" #include "nativehelper/ScopedUtfChars.h" #include "oat_file.h" @@ -195,6 +197,56 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasJitCompiledCode(JNIEnv* env, return jit->GetCodeCache()->ContainsMethod(method); } +static void ForceJitCompiled(Thread* self, ArtMethod* method) REQUIRES(!Locks::mutator_lock_) { + { + ScopedObjectAccess soa(self); + if (method->IsNative()) { + std::string msg(method->PrettyMethod()); + msg += ": is native"; + ThrowIllegalArgumentException(msg.c_str()); + return; + } else if (!Runtime::Current()->GetRuntimeCallbacks()->IsMethodSafeToJit(method)) { + std::string msg(method->PrettyMethod()); + msg += ": is not safe to jit!"; + ThrowIllegalStateException(msg.c_str()); + return; + } + } + jit::Jit* jit = GetJitIfEnabled(); + jit::JitCodeCache* code_cache = jit->GetCodeCache(); + // Update the code cache to make sure the JIT code does not get deleted. + // Note: this will apply to all JIT compilations. + code_cache->SetGarbageCollectCode(false); + while (true) { + if (code_cache->WillExecuteJitCode(method)) { + break; + } else { + // Sleep to yield to the compiler thread. + usleep(1000); + ScopedObjectAccess soa(self); + // Make sure there is a profiling info, required by the compiler. + ProfilingInfo::Create(self, method, /* retry_allocation */ true); + // Will either ensure it's compiled or do the compilation itself. + jit->CompileMethod(method, self, /* osr */ false); + } + } +} + +extern "C" JNIEXPORT void JNICALL Java_Main_ensureMethodJitCompiled(JNIEnv*, jclass, jobject meth) { + jit::Jit* jit = GetJitIfEnabled(); + if (jit == nullptr) { + return; + } + + Thread* self = Thread::Current(); + ArtMethod* method; + { + ScopedObjectAccess soa(self); + method = ArtMethod::FromReflectedMethod(soa, meth); + } + ForceJitCompiled(self, method); +} + extern "C" JNIEXPORT void JNICALL Java_Main_ensureJitCompiled(JNIEnv* env, jclass, jclass cls, @@ -219,24 +271,7 @@ extern "C" JNIEXPORT void JNICALL Java_Main_ensureJitCompiled(JNIEnv* env, } DCHECK(method != nullptr) << "Unable to find method called " << chars.c_str(); } - - jit::JitCodeCache* code_cache = jit->GetCodeCache(); - // Update the code cache to make sure the JIT code does not get deleted. - // Note: this will apply to all JIT compilations. - code_cache->SetGarbageCollectCode(false); - while (true) { - if (code_cache->WillExecuteJitCode(method)) { - break; - } else { - // Sleep to yield to the compiler thread. - usleep(1000); - ScopedObjectAccess soa(self); - // Make sure there is a profiling info, required by the compiler. - ProfilingInfo::Create(self, method, /* retry_allocation */ true); - // Will either ensure it's compiled or do the compilation itself. - jit->CompileMethod(method, self, /* osr */ false); - } - } + ForceJitCompiled(self, method); } extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasSingleImplementation(JNIEnv* env, diff --git a/test/knownfailures.json b/test/knownfailures.json index 8aa59793d5..d769b48bfd 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -1090,6 +1090,14 @@ "description": ["We do not inline with debuggable."] }, { + "tests": ["1955-pop-frame-jit-called", "1956-pop-frame-jit-calling"], + "variant": "jit-on-first-use", + "description": [ + "These tests directly set -Xjitthreshold:1000 to prevent the jit from compiling any", + "extra methods. jit-at-first-use would disrupt this." + ] + }, + { "tests": ["135-MirandaDispatch"], "variant": "interp-ac & 32 & host", "env_vars": {"SANITIZE_HOST": "address"}, diff --git a/test/run-test b/test/run-test index 4a4a64c764..229e2019dd 100755 --- a/test/run-test +++ b/test/run-test @@ -757,7 +757,7 @@ fi echo "${test_dir}: building..." 1>&2 rm -rf "$tmp_dir" -cp -Rp "$test_dir" "$tmp_dir" +cp -LRp "$test_dir" "$tmp_dir" cd "$tmp_dir" if [ '!' -r "$build" ]; then |