diff options
74 files changed, 5223 insertions, 274 deletions
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc index e889f9872c..e4ce825c2a 100644 --- a/openjdkjvmti/OpenjdkJvmTi.cc +++ b/openjdkjvmti/OpenjdkJvmTi.cc @@ -344,50 +344,40 @@ class JvmtiFunctions { return StackUtil::NotifyFramePop(env, thread, depth); } - static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jobject value ATTRIBUTE_UNUSED) { + static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env, jthread thread, jobject value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_force_early_return); - return ERR(NOT_IMPLEMENTED); + return StackUtil::ForceEarlyReturn(env, gEventHandler, thread, value); } - static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint value ATTRIBUTE_UNUSED) { + static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env, jthread thread, jint value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_force_early_return); - return ERR(NOT_IMPLEMENTED); + return StackUtil::ForceEarlyReturn(env, gEventHandler, thread, value); } - static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jlong value ATTRIBUTE_UNUSED) { + static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env, jthread thread, jlong value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_force_early_return); - return ERR(NOT_IMPLEMENTED); + return StackUtil::ForceEarlyReturn(env, gEventHandler, thread, value); } - static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jfloat value ATTRIBUTE_UNUSED) { + static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env, jthread thread, jfloat value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_force_early_return); - return ERR(NOT_IMPLEMENTED); + return StackUtil::ForceEarlyReturn(env, gEventHandler, thread, value); } - static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jdouble value ATTRIBUTE_UNUSED) { + static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env, jthread thread, jdouble value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_force_early_return); - return ERR(NOT_IMPLEMENTED); + return StackUtil::ForceEarlyReturn(env, gEventHandler, thread, value); } - static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) { + static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_force_early_return); - return ERR(NOT_IMPLEMENTED); + return StackUtil::ForceEarlyReturn<nullptr_t>(env, gEventHandler, thread, nullptr); } static jvmtiError FollowReferences(jvmtiEnv* env, diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h index 7433e54eda..083ba6ddf1 100644 --- a/openjdkjvmti/art_jvmti.h +++ b/openjdkjvmti/art_jvmti.h @@ -278,7 +278,7 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_native_method_bind_events = 1, .can_generate_garbage_collection_events = 1, .can_generate_object_free_events = 1, - .can_force_early_return = 0, + .can_force_early_return = 1, .can_get_owned_monitor_stack_depth_info = 1, .can_get_constant_pool = 0, .can_set_native_method_prefix = 0, @@ -296,6 +296,7 @@ const jvmtiCapabilities kPotentialCapabilities = { // can_redefine_any_class: // can_redefine_classes: // can_pop_frame: +// can_force_early_return: // 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. @@ -333,7 +334,7 @@ const jvmtiCapabilities kNonDebuggableUnsupportedCapabilities = { .can_generate_native_method_bind_events = 0, .can_generate_garbage_collection_events = 0, .can_generate_object_free_events = 0, - .can_force_early_return = 0, + .can_force_early_return = 1, .can_get_owned_monitor_stack_depth_info = 0, .can_get_constant_pool = 0, .can_set_native_method_prefix = 0, diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h index 8e06fe3a24..627129a20b 100644 --- a/openjdkjvmti/events-inl.h +++ b/openjdkjvmti/events-inl.h @@ -362,7 +362,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() && + !frame->GetSkipMethodExitEvents() && ShouldDispatchOnThread<ArtJvmtiEvent::kFramePop>(env, thread); } @@ -619,6 +619,7 @@ inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, return (added && caps.can_access_local_variables == 1) || caps.can_generate_breakpoint_events == 1 || caps.can_pop_frame == 1 || + caps.can_force_early_return == 1 || (caps.can_retransform_classes == 1 && IsEventEnabledAnywhere(event) && env->event_masks.IsEnabledAnywhere(event)); @@ -639,7 +640,7 @@ inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, if (caps.can_generate_breakpoint_events == 1) { HandleBreakpointEventsChanged(added); } - if (caps.can_pop_frame == 1 && added) { + if ((caps.can_pop_frame == 1 || caps.can_force_early_return == 1) && added) { // TODO We should keep track of how many of these have been enabled and remove it if there are // no more possible users. This isn't expected to be too common. art::Runtime::Current()->SetNonStandardExitsEnabled(); diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 40e8b801c0..7174e1b671 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -29,9 +29,14 @@ * questions. */ +#include <android-base/thread_annotations.h> + +#include "base/locks.h" +#include "base/mutex.h" #include "events-inl.h" #include <array> +#include <functional> #include <sys/time.h> #include "arch/context.h" @@ -41,21 +46,29 @@ #include "base/mutex.h" #include "deopt_manager.h" #include "dex/dex_file_types.h" +#include "events.h" #include "gc/allocation_listener.h" #include "gc/gc_pause_listener.h" #include "gc/heap.h" #include "gc/scoped_gc_critical_section.h" #include "handle_scope-inl.h" +#include "indirect_reference_table.h" #include "instrumentation.h" +#include "interpreter/shadow_frame.h" #include "jni/jni_env_ext-inl.h" #include "jni/jni_internal.h" +#include "jvalue-inl.h" +#include "jvalue.h" +#include "jvmti.h" #include "mirror/class.h" #include "mirror/object-inl.h" #include "monitor-inl.h" #include "nativehelper/scoped_local_ref.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" +#include "scoped_thread_state_change.h" #include "stack.h" +#include "thread.h" #include "thread-inl.h" #include "thread_list.h" #include "ti_phase.h" @@ -571,7 +584,34 @@ static void SetupGcPauseTracking(JvmtiGcPauseListener* listener, ArtJvmtiEvent e class JvmtiMethodTraceListener final : public art::instrumentation::InstrumentationListener { public: - explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {} + explicit JvmtiMethodTraceListener(EventHandler* handler) + : event_handler_(handler), + non_standard_exits_lock_("JVMTI NonStandard Exits list lock", + art::LockLevel::kGenericBottomLock) {} + + void AddDelayedNonStandardExitEvent(const art::ShadowFrame* frame, bool is_object, jvalue val) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(art::Locks::user_code_suspension_lock_, art::Locks::thread_list_lock_) { + art::Thread* self = art::Thread::Current(); + jobject to_cleanup = nullptr; + jobject new_val = is_object ? self->GetJniEnv()->NewGlobalRef(val.l) : nullptr; + { + art::MutexLock mu(self, non_standard_exits_lock_); + NonStandardExitEventInfo saved{ nullptr, { .j = 0 } }; + if (is_object) { + saved.return_val_obj_ = new_val; + saved.return_val_.l = saved.return_val_obj_; + } else { + saved.return_val_.j = val.j; + } + // only objects need cleanup. + if (UNLIKELY(is_object && non_standard_exits_.find(frame) != non_standard_exits_.end())) { + to_cleanup = non_standard_exits_.find(frame)->second.return_val_obj_; + } + non_standard_exits_.insert_or_assign(frame, saved); + } + self->GetJniEnv()->DeleteGlobalRef(to_cleanup); + } // Call-back for when a method is entered. void MethodEntered(art::Thread* self, @@ -589,15 +629,44 @@ class JvmtiMethodTraceListener final : public art::instrumentation::Instrumentat } } + // TODO Maybe try to combine this with below using templates? // Callback for when a method is exited with a reference return value. void MethodExited(art::Thread* self, art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, art::ArtMethod* method, uint32_t dex_pc ATTRIBUTE_UNUSED, - art::Handle<art::mirror::Object> return_value) + art::instrumentation::OptionalFrame frame, + art::MutableHandle<art::mirror::Object>& return_value) REQUIRES_SHARED(art::Locks::mutator_lock_) override { - if (!method->IsRuntimeMethod() && - event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { + if (method->IsRuntimeMethod()) { + return; + } + if (frame.has_value() && UNLIKELY(event_handler_->IsEventEnabledAnywhere( + ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue))) { + DCHECK(!frame->get().GetSkipMethodExitEvents()); + bool has_return = false; + jobject ret_val = nullptr; + { + art::MutexLock mu(self, non_standard_exits_lock_); + const art::ShadowFrame* sframe = &frame.value().get(); + const auto it = non_standard_exits_.find(sframe); + if (it != non_standard_exits_.end()) { + ret_val = it->second.return_val_obj_; + return_value.Assign(self->DecodeJObject(ret_val)); + non_standard_exits_.erase(it); + has_return = true; + } + } + if (has_return) { + ScopedLocalRef<jthread> thr(self->GetJniEnv(), + self->GetJniEnv()->NewLocalRef(self->GetPeer())); + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + self->GetJniEnv()->DeleteGlobalRef(ret_val); + event_handler_->SetInternalEvent( + thr.get(), ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue, JVMTI_DISABLE); + } + } + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { DCHECK_EQ( method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize)->GetReturnTypePrimitive(), art::Primitive::kPrimNot) << method->PrettyMethod(); @@ -621,14 +690,36 @@ class JvmtiMethodTraceListener final : public art::instrumentation::Instrumentat art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, art::ArtMethod* method, uint32_t dex_pc ATTRIBUTE_UNUSED, - const art::JValue& return_value) - REQUIRES_SHARED(art::Locks::mutator_lock_) override { - if (!method->IsRuntimeMethod() && - event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { + art::instrumentation::OptionalFrame frame, + art::JValue& return_value) REQUIRES_SHARED(art::Locks::mutator_lock_) override { + if (frame.has_value() && + UNLIKELY(event_handler_->IsEventEnabledAnywhere( + ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue))) { + DCHECK(!frame->get().GetSkipMethodExitEvents()); + bool has_return = false; + { + art::MutexLock mu(self, non_standard_exits_lock_); + const art::ShadowFrame* sframe = &frame.value().get(); + const auto it = non_standard_exits_.find(sframe); + if (it != non_standard_exits_.end()) { + return_value.SetJ(it->second.return_val_.j); + non_standard_exits_.erase(it); + has_return = true; + } + } + if (has_return) { + ScopedLocalRef<jthread> thr(self->GetJniEnv(), + self->GetJniEnv()->NewLocalRef(self->GetPeer())); + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + event_handler_->SetInternalEvent( + thr.get(), ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue, JVMTI_DISABLE); + } + } + if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) { DCHECK_NE( method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize)->GetReturnTypePrimitive(), art::Primitive::kPrimNot) << method->PrettyMethod(); - DCHECK(!self->IsExceptionPending()); + DCHECK(!self->IsExceptionPending()) << self->GetException()->Dump(); jvalue val; art::JNIEnvExt* jnienv = self->GetJniEnv(); // 64bit integer is the largest value in the union so we should be fine simply copying it into @@ -944,23 +1035,65 @@ class JvmtiMethodTraceListener final : public art::instrumentation::Instrumentat } private: + struct NonStandardExitEventInfo { + // if non-null is a GlobalReference to the returned value. + jobject return_val_obj_; + // The return-value to be passed to the MethodExit event. + jvalue return_val_; + }; + EventHandler* const event_handler_; + + mutable art::Mutex non_standard_exits_lock_ + ACQUIRED_BEFORE(art::Locks::instrument_entrypoints_lock_); + + std::unordered_map<const art::ShadowFrame*, NonStandardExitEventInfo> non_standard_exits_ + GUARDED_BY(non_standard_exits_lock_); }; -static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { +uint32_t EventHandler::GetInstrumentationEventsFor(ArtJvmtiEvent event) { switch (event) { case ArtJvmtiEvent::kMethodEntry: return art::instrumentation::Instrumentation::kMethodEntered; - case ArtJvmtiEvent::kMethodExit: - return art::instrumentation::Instrumentation::kMethodExited | - art::instrumentation::Instrumentation::kMethodUnwind; + case ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue: + // TODO We want to do this but supporting only having a single one is difficult. + // return art::instrumentation::Instrumentation::kMethodExited; + case ArtJvmtiEvent::kMethodExit: { + DCHECK(event == ArtJvmtiEvent::kMethodExit || + event == ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue) + << "event = " << static_cast<uint32_t>(event); + ArtJvmtiEvent other = event == ArtJvmtiEvent::kMethodExit + ? ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue + : ArtJvmtiEvent::kMethodExit; + if (LIKELY(!IsEventEnabledAnywhere(other))) { + return art::instrumentation::Instrumentation::kMethodExited | + art::instrumentation::Instrumentation::kMethodUnwind; + } else { + // The event needs to be kept around/is already enabled by the other jvmti event that uses + // the same instrumentation event. + return 0u; + } + } case ArtJvmtiEvent::kFieldModification: return art::instrumentation::Instrumentation::kFieldWritten; case ArtJvmtiEvent::kFieldAccess: return art::instrumentation::Instrumentation::kFieldRead; case ArtJvmtiEvent::kBreakpoint: - case ArtJvmtiEvent::kSingleStep: - return art::instrumentation::Instrumentation::kDexPcMoved; + case ArtJvmtiEvent::kSingleStep: { + // Need to skip adding the listeners if the event is breakpoint/single-step since those events + // share the same art-instrumentation underlying event. We need to give them their own deopt + // request though so the test waits until here. + DCHECK(event == ArtJvmtiEvent::kBreakpoint || event == ArtJvmtiEvent::kSingleStep); + ArtJvmtiEvent other = event == ArtJvmtiEvent::kBreakpoint ? ArtJvmtiEvent::kSingleStep + : ArtJvmtiEvent::kBreakpoint; + if (LIKELY(!IsEventEnabledAnywhere(other))) { + return art::instrumentation::Instrumentation::kDexPcMoved; + } else { + // The event needs to be kept around/is already enabled by the other jvmti event that uses + // the same instrumentation event. + return 0u; + } + } case ArtJvmtiEvent::kFramePop: return art::instrumentation::Instrumentation::kWatchedFramePop; case ArtJvmtiEvent::kException: @@ -999,6 +1132,7 @@ static DeoptRequirement GetDeoptRequirement(ArtJvmtiEvent event, jthread thread) case ArtJvmtiEvent::kFieldAccess: case ArtJvmtiEvent::kSingleStep: case ArtJvmtiEvent::kFramePop: + case ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue: return thread == nullptr ? DeoptRequirement::kFull : DeoptRequirement::kThread; case ArtJvmtiEvent::kVmInit: case ArtJvmtiEvent::kVmDeath: @@ -1076,18 +1210,8 @@ void EventHandler::SetupTraceListener(JvmtiMethodTraceListener* listener, bool enable) { // Add the actual listeners. uint32_t new_events = GetInstrumentationEventsFor(event); - if (new_events == art::instrumentation::Instrumentation::kDexPcMoved) { - // Need to skip adding the listeners if the event is breakpoint/single-step since those events - // share the same art-instrumentation underlying event. We need to give them their own deopt - // request though so the test waits until here. - DCHECK(event == ArtJvmtiEvent::kBreakpoint || event == ArtJvmtiEvent::kSingleStep); - ArtJvmtiEvent other = event == ArtJvmtiEvent::kBreakpoint ? ArtJvmtiEvent::kSingleStep - : ArtJvmtiEvent::kBreakpoint; - if (IsEventEnabledAnywhere(other)) { - // The event needs to be kept around/is already enabled by the other jvmti event that uses the - // same instrumentation event. - return; - } + if (new_events == 0) { + return; } art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative); art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); @@ -1204,6 +1328,7 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { case ArtJvmtiEvent::kExceptionCatch: case ArtJvmtiEvent::kBreakpoint: case ArtJvmtiEvent::kSingleStep: + case ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue: SetupTraceListener(method_trace_listener_.get(), event, enable); return; case ArtJvmtiEvent::kMonitorContendedEnter: @@ -1278,6 +1403,90 @@ static bool HasAssociatedCapability(ArtJvmTiEnv* env, } } +static bool IsInternalEvent(ArtJvmtiEvent event) { + return static_cast<uint32_t>(event) >= + static_cast<uint32_t>(ArtJvmtiEvent::kMinInternalEventTypeVal); +} + +jvmtiError EventHandler::SetInternalEvent(jthread thread, + ArtJvmtiEvent event, + jvmtiEventMode mode) { + CHECK(IsInternalEvent(event)) << static_cast<uint32_t>(event); + + art::Thread* self = art::Thread::Current(); + art::Thread* target = nullptr; + ScopedNoUserCodeSuspension snucs(self); + // The overall state across all threads and jvmtiEnvs. This is used to control the state of the + // instrumentation handlers since we only want each added once. + bool old_state; + bool new_state; + // The state for just the current 'thread' (including null) across all jvmtiEnvs. This is used to + // control the deoptimization state since we do refcounting for that and need to perform different + // actions depending on if the event is limited to a single thread or global. + bool old_thread_state; + bool new_thread_state; + { + // 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::WriterMutexLock el_mu(self, envs_lock_); + art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_); + jvmtiError err = ERR(INTERNAL); + if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) { + return err; + } else if (target->IsStillStarting() || target->GetState() == art::ThreadState::kStarting) { + target->Dump(LOG_STREAM(WARNING) << "Is not alive: "); + return ERR(THREAD_NOT_ALIVE); + } + + // Make sure we have a valid jthread to pass to deopt-manager. + ScopedLocalRef<jthread> thread_lr( + soa.Env(), thread != nullptr ? nullptr : soa.AddLocalReference<jthread>(target->GetPeer())); + if (thread == nullptr) { + thread = thread_lr.get(); + } + CHECK(thread != nullptr); + + { + DCHECK_GE(GetInternalEventRefcount(event) + (mode == JVMTI_ENABLE ? 1 : -1), 0) + << "Refcount: " << GetInternalEventRefcount(event); + DCHECK_GE(GetInternalEventThreadRefcount(event, target) + (mode == JVMTI_ENABLE ? 1 : -1), 0) + << "Refcount: " << GetInternalEventThreadRefcount(event, target); + DCHECK_GE(GetInternalEventRefcount(event), GetInternalEventThreadRefcount(event, target)); + old_state = GetInternalEventRefcount(event) > 0; + old_thread_state = GetInternalEventThreadRefcount(event, target) > 0; + if (mode == JVMTI_ENABLE) { + new_state = IncrInternalEventRefcount(event) > 0; + new_thread_state = IncrInternalEventThreadRefcount(event, target) > 0; + } else { + new_state = DecrInternalEventRefcount(event) > 0; + new_thread_state = DecrInternalEventThreadRefcount(event, target) > 0; + } + if (old_state != new_state) { + global_mask.Set(event, new_state); + } + } + } + // Handle any special work required for the event type. We still have the + // user_code_suspend_count_lock_ so there won't be any interleaving here. + if (new_state != old_state) { + HandleEventType(event, mode == JVMTI_ENABLE); + } + if (old_thread_state != new_thread_state) { + HandleEventDeopt(event, thread, new_thread_state); + } + return OK; +} + +static bool IsDirectlySettableEvent(ArtJvmtiEvent event) { + return !IsInternalEvent(event); +} + +static bool EventIsNormal(ArtJvmtiEvent event) { + return EventMask::EventIsInRange(event) && IsDirectlySettableEvent(event); +} + jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, jthread thread, ArtJvmtiEvent event, @@ -1286,7 +1495,7 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, return ERR(ILLEGAL_ARGUMENT); } - if (!EventMask::EventIsInRange(event)) { + if (!EventIsNormal(event)) { return ERR(INVALID_EVENT_TYPE); } @@ -1385,6 +1594,46 @@ void EventHandler::HandleBreakpointEventsChanged(bool added) { } } +void EventHandler::AddDelayedNonStandardExitEvent(const art::ShadowFrame *frame, + bool is_object, + jvalue val) { + method_trace_listener_->AddDelayedNonStandardExitEvent(frame, is_object, val); +} + +static size_t GetInternalEventIndex(ArtJvmtiEvent event) { + CHECK(IsInternalEvent(event)); + return static_cast<size_t>(event) - static_cast<size_t>(ArtJvmtiEvent::kMinInternalEventTypeVal); +} + +int32_t EventHandler::DecrInternalEventThreadRefcount(ArtJvmtiEvent event, art::Thread* target) { + return --GetInternalEventThreadRefcount(event, target); +} + +int32_t EventHandler::IncrInternalEventThreadRefcount(ArtJvmtiEvent event, art::Thread* target) { + return ++GetInternalEventThreadRefcount(event, target); +} + +int32_t& EventHandler::GetInternalEventThreadRefcount(ArtJvmtiEvent event, art::Thread* target) { + auto& refs = internal_event_thread_refcount_[GetInternalEventIndex(event)]; + UniqueThread target_ut{target, target->GetTid()}; + if (refs.find(target_ut) == refs.end()) { + refs.insert({target_ut, 0}); + } + return refs.at(target_ut); +} + +int32_t EventHandler::DecrInternalEventRefcount(ArtJvmtiEvent event) { + return --internal_event_refcount_[GetInternalEventIndex(event)]; +} + +int32_t EventHandler::IncrInternalEventRefcount(ArtJvmtiEvent event) { + return ++internal_event_refcount_[GetInternalEventIndex(event)]; +} + +int32_t EventHandler::GetInternalEventRefcount(ArtJvmtiEvent event) const { + return internal_event_refcount_[GetInternalEventIndex(event)]; +} + void EventHandler::Shutdown() { // Need to remove the method_trace_listener_ if it's there. art::Thread* self = art::Thread::Current(); @@ -1398,7 +1647,8 @@ void EventHandler::Shutdown() { EventHandler::EventHandler() : envs_lock_("JVMTI Environment List Lock", art::LockLevel::kPostMutatorTopLockLevel), - frame_pop_enabled(false) { + frame_pop_enabled(false), + internal_event_refcount_({0}) { alloc_listener_.reset(new JvmtiAllocationListener(this)); ddm_listener_.reset(new JvmtiDdmChunkListener(this)); gc_pause_listener_.reset(new JvmtiGcPauseListener(this)); diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index d54c87aceb..ac86d0cd9b 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -18,14 +18,17 @@ #define ART_OPENJDKJVMTI_EVENTS_H_ #include <bitset> +#include <unordered_map> #include <vector> #include <android-base/logging.h> #include <android-base/thread_annotations.h> +#include "android-base/thread_annotations.h" #include "base/macros.h" #include "base/mutex.h" #include "jvmti.h" +#include "managed_stack.h" #include "thread.h" namespace openjdkjvmti { @@ -73,17 +76,43 @@ enum class ArtJvmtiEvent : jint { kGarbageCollectionFinish = JVMTI_EVENT_GARBAGE_COLLECTION_FINISH, kObjectFree = JVMTI_EVENT_OBJECT_FREE, kVmObjectAlloc = JVMTI_EVENT_VM_OBJECT_ALLOC, + // Internal event to mark a ClassFileLoadHook as one created with the can_retransform_classes + // capability. kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1, kDdmPublishChunk = JVMTI_MAX_EVENT_TYPE_VAL + 2, - kMaxEventTypeVal = kDdmPublishChunk, + kMaxNormalEventTypeVal = kDdmPublishChunk, + + // All that follow are events used to implement internal JVMTI functions. They are not settable + // directly by agents. + kMinInternalEventTypeVal = kMaxNormalEventTypeVal + 1, + + // Internal event we use to implement the ForceEarlyReturn functions. + kForceEarlyReturnUpdateReturnValue = kMinInternalEventTypeVal, + kMaxInternalEventTypeVal = kForceEarlyReturnUpdateReturnValue, + + kMaxEventTypeVal = kMaxInternalEventTypeVal, }; +constexpr jint kInternalEventCount = static_cast<jint>(ArtJvmtiEvent::kMaxInternalEventTypeVal) - + static_cast<jint>(ArtJvmtiEvent::kMinInternalEventTypeVal) + 1; + using ArtJvmtiEventDdmPublishChunk = void (*)(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jint data_type, jint data_len, const jbyte* data); +// It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the +// thread id. +// Note: We could just use the tid like tracing does. +using UniqueThread = std::pair<art::Thread*, uint32_t>; + +struct UniqueThreadHasher { + std::size_t operator()(const UniqueThread& k) const { + return std::hash<uint32_t>{}(k.second) ^ (std::hash<void*>{}(k.first) << 1); + } +}; + struct ArtJvmtiEventCallbacks : jvmtiEventCallbacks { ArtJvmtiEventCallbacks() : DdmPublishChunk(nullptr) { memset(this, 0, sizeof(jvmtiEventCallbacks)); @@ -141,10 +170,6 @@ struct EventMasks { // The per-thread enabled events. - // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the - // thread id. - // Note: We could just use the tid like tracing does. - using UniqueThread = std::pair<art::Thread*, uint32_t>; // TODO: Native thread objects are immovable, so we can use them as keys in an (unordered) map, // if necessary. std::vector<std::pair<UniqueThread, EventMask>> thread_event_masks; @@ -198,6 +223,16 @@ class EventHandler { return global_mask.Test(event); } + // Sets an internal event. Unlike normal JVMTI events internal events are not associated with any + // particular jvmtiEnv and are refcounted. This refcounting is done to allow us to easily enable + // events during functions and disable them during the requested event callback. Since these are + // used to implement various JVMTI functions these events always have a single target thread. If + // target is null the current thread is used. + jvmtiError SetInternalEvent(jthread target, + ArtJvmtiEvent event, + jvmtiEventMode mode) + REQUIRES(!envs_lock_, !art::Locks::mutator_lock_); + jvmtiError SetEvent(ArtJvmTiEnv* env, jthread thread, ArtJvmtiEvent event, @@ -246,9 +281,15 @@ class EventHandler { inline void DispatchEventOnEnv(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const REQUIRES(!envs_lock_); + void AddDelayedNonStandardExitEvent(const art::ShadowFrame* frame, bool is_object, jvalue val) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(art::Locks::user_code_suspension_lock_, art::Locks::thread_list_lock_); + private: void SetupTraceListener(JvmtiMethodTraceListener* listener, ArtJvmtiEvent event, bool enable); + uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event); + // Specifically handle the FramePop event which it might not always be possible to turn off. void SetupFramePopTraceListener(bool enable); @@ -325,6 +366,21 @@ class EventHandler { bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event); + int32_t GetInternalEventRefcount(ArtJvmtiEvent event) const REQUIRES(envs_lock_); + // Increment internal event refcount for the given event and return the new count. + int32_t IncrInternalEventRefcount(ArtJvmtiEvent event) REQUIRES(envs_lock_); + // Decrement internal event refcount for the given event and return the new count. + int32_t DecrInternalEventRefcount(ArtJvmtiEvent event) REQUIRES(envs_lock_); + + int32_t& GetInternalEventThreadRefcount(ArtJvmtiEvent event, art::Thread* target) + REQUIRES(envs_lock_, art::Locks::thread_list_lock_); + // Increment internal event refcount for the given event and return the new count. + int32_t IncrInternalEventThreadRefcount(ArtJvmtiEvent event, art::Thread* target) + REQUIRES(envs_lock_, art::Locks::thread_list_lock_); + // Decrement internal event refcount for the given event and return the new count. + int32_t DecrInternalEventThreadRefcount(ArtJvmtiEvent event, art::Thread* target) + REQUIRES(envs_lock_, art::Locks::thread_list_lock_); + // List of all JvmTiEnv objects that have been created, in their creation order. It is a std::list // since we mostly access it by iterating over the entire thing, only ever append to the end, and // need to be able to remove arbitrary elements from it. @@ -348,6 +404,16 @@ class EventHandler { // continue to listen to this event even if it has been disabled. // TODO We could remove the listeners once all jvmtiEnvs have drained their shadow-frame vectors. bool frame_pop_enabled; + + // The overall refcount for each internal event across all threads. + std::array<int32_t, kInternalEventCount> internal_event_refcount_ GUARDED_BY(envs_lock_); + // The refcount for each thread for each internal event. + // TODO We should clean both this and the normal EventMask lists up when threads end. + std::array<std::unordered_map<UniqueThread, int32_t, UniqueThreadHasher>, kInternalEventCount> + internal_event_thread_refcount_ + GUARDED_BY(envs_lock_) GUARDED_BY(art::Locks::thread_list_lock_); + + friend class JvmtiMethodTraceListener; }; } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc index 75f055652e..38257f1d6a 100644 --- a/openjdkjvmti/ti_stack.cc +++ b/openjdkjvmti/ti_stack.cc @@ -32,10 +32,13 @@ #include "ti_stack.h" #include <algorithm> +#include <initializer_list> #include <list> #include <unordered_map> #include <vector> +#include "android-base/macros.h" +#include "android-base/thread_annotations.h" #include "arch/context.h" #include "art_field-inl.h" #include "art_method-inl.h" @@ -44,21 +47,35 @@ #include "barrier.h" #include "base/bit_utils.h" #include "base/enums.h" +#include "base/locks.h" +#include "base/macros.h" #include "base/mutex.h" #include "deopt_manager.h" #include "dex/code_item_accessors-inl.h" #include "dex/dex_file.h" #include "dex/dex_file_annotations.h" #include "dex/dex_file_types.h" +#include "dex/dex_instruction-inl.h" +#include "dex/primitive.h" +#include "events.h" #include "gc_root.h" #include "handle_scope-inl.h" +#include "instrumentation.h" +#include "interpreter/shadow_frame-inl.h" +#include "interpreter/shadow_frame.h" #include "jni/jni_env_ext.h" #include "jni/jni_internal.h" +#include "jvalue-inl.h" +#include "jvalue.h" +#include "jvmti.h" #include "mirror/class.h" #include "mirror/dex_cache.h" #include "nativehelper/scoped_local_ref.h" #include "scoped_thread_state_change-inl.h" +#include "scoped_thread_state_change.h" #include "stack.h" +#include "thread.h" +#include "thread_state.h" #include "ti_logging.h" #include "ti_thread.h" #include "thread-current-inl.h" @@ -1087,96 +1104,333 @@ jvmtiError StackUtil::NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) return OK; } -jvmtiError StackUtil::PopFrame(jvmtiEnv* env, jthread thread) { - art::Thread* self = art::Thread::Current(); - art::Thread* target; +namespace { - ScopedNoUserCodeSuspension snucs(self); - // 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::Locks::thread_list_lock_->ExclusiveLock(self); - jvmtiError err = ERR(INTERNAL); - if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) { - art::Locks::thread_list_lock_->ExclusiveUnlock(self); - return err; - } - { - art::Locks::thread_suspend_count_lock_->ExclusiveLock(self); - if (target == self || target->GetUserCodeSuspendCount() == 0) { - // We cannot be the current thread for this function. - art::Locks::thread_suspend_count_lock_->ExclusiveUnlock(self); - art::Locks::thread_list_lock_->ExclusiveUnlock(self); - return ERR(THREAD_NOT_SUSPENDED); +enum class NonStandardExitType { + kPopFrame, + kForceReturn, +}; + +template<NonStandardExitType kExitType> +class NonStandardExitFrames { + public: + NonStandardExitFrames(art::Thread* self, jvmtiEnv* env, jthread thread) + REQUIRES(!art::Locks::thread_suspend_count_lock_) + ACQUIRE_SHARED(art::Locks::mutator_lock_) + ACQUIRE(art::Locks::thread_list_lock_, art::Locks::user_code_suspension_lock_) + : snucs_(self) { + // We keep the user-code-suspend-count lock. + art::Locks::user_code_suspension_lock_->AssertExclusiveHeld(self); + + // 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. + old_state_ = self->TransitionFromSuspendedToRunnable(); + art::ScopedObjectAccessUnchecked soau(self); + + art::Locks::thread_list_lock_->ExclusiveLock(self); + + if (!ThreadUtil::GetAliveNativeThread(thread, soau, &target_, &result_)) { + return; } - art::Locks::thread_suspend_count_lock_->ExclusiveUnlock(self); + { + 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. + result_ = ERR(THREAD_NOT_SUSPENDED); + return; + } + } + 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)) { + JVMTI_LOG(WARNING, env) << "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."; + result_ = ERR(OPAQUE_FRAME); + return; + } + // 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! + JVMTI_LOG(INFO, env) << "Can not pop final frame off of a stack"; + result_ = ERR(NO_MORE_FRAMES); + return; + } + + art::ArtMethod* called_method = final_frame.GetMethod(); + art::ArtMethod* calling_method = penultimate_frame.GetMethod(); + if (!CheckFunctions(env, calling_method, called_method)) { + return; + } + DCHECK(!called_method->IsNative()) << called_method->PrettyMethod(); + + // From here we are sure to succeed. + result_ = OK; + + // Get/create a shadow frame + final_frame_ = final_frame.GetOrCreateShadowFrame(&created_final_frame_); + penultimate_frame_ = + (calling_method->IsNative() + ? nullptr + : penultimate_frame.GetOrCreateShadowFrame(&created_penultimate_frame_)); + + final_frame_id_ = final_frame.GetFrameId(); + penultimate_frame_id_ = penultimate_frame.GetFrameId(); + + CHECK_NE(final_frame_, penultimate_frame_) << "Frames at different depths not different!"; } - 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)) { - JVMTI_LOG(WARNING, env) - << "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."; - art::Locks::thread_list_lock_->ExclusiveUnlock(self); - return ERR(OPAQUE_FRAME); + + bool CheckFunctions(jvmtiEnv* env, art::ArtMethod* calling, art::ArtMethod* called) + REQUIRES(art::Locks::thread_list_lock_, art::Locks::user_code_suspension_lock_) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + ~NonStandardExitFrames() RELEASE_SHARED(art::Locks::mutator_lock_) + REQUIRES(!art::Locks::thread_list_lock_) + RELEASE(art::Locks::user_code_suspension_lock_) { + art::Thread* self = art::Thread::Current(); + DCHECK_EQ(old_state_, art::ThreadState::kNative) + << "Unexpected thread state on entering PopFrame!"; + self->TransitionFromRunnableToSuspended(old_state_); + } + + ScopedNoUserCodeSuspension snucs_; + art::ShadowFrame* final_frame_ GUARDED_BY(art::Locks::user_code_suspension_lock_) = nullptr; + art::ShadowFrame* penultimate_frame_ GUARDED_BY(art::Locks::user_code_suspension_lock_) = nullptr; + bool created_final_frame_ GUARDED_BY(art::Locks::user_code_suspension_lock_) = false; + bool created_penultimate_frame_ GUARDED_BY(art::Locks::user_code_suspension_lock_) = false; + uint32_t final_frame_id_ GUARDED_BY(art::Locks::user_code_suspension_lock_) = -1; + uint32_t penultimate_frame_id_ GUARDED_BY(art::Locks::user_code_suspension_lock_) = -1; + art::Thread* target_ GUARDED_BY(art::Locks::thread_list_lock_) = nullptr; + art::ThreadState old_state_ = art::ThreadState::kTerminated; + jvmtiError result_ = ERR(INTERNAL); +}; + +template <> +bool NonStandardExitFrames<NonStandardExitType::kForceReturn>::CheckFunctions( + jvmtiEnv* env, art::ArtMethod* calling ATTRIBUTE_UNUSED, art::ArtMethod* called) { + if (UNLIKELY(called->IsNative())) { + result_ = ERR(OPAQUE_FRAME); + JVMTI_LOG(INFO, env) << "Cannot force early return from " << called->PrettyMethod() + << " because it is native."; + return false; + } else { + return true; } - // 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! - art::Locks::thread_list_lock_->ExclusiveUnlock(self); - return ERR(NO_MORE_FRAMES); +template <> +bool NonStandardExitFrames<NonStandardExitType::kPopFrame>::CheckFunctions( + jvmtiEnv* env, art::ArtMethod* calling, art::ArtMethod* called) { + if (UNLIKELY(calling->IsNative() || called->IsNative())) { + result_ = ERR(OPAQUE_FRAME); + JVMTI_LOG(INFO, env) << "Cannot force early return from " << called->PrettyMethod() << " to " + << calling->PrettyMethod() << " because at least one of them is native."; + return false; + } else { + return true; } +} - art::ArtMethod* called_method = final_frame.GetMethod(); - art::ArtMethod* calling_method = penultimate_frame.GetMethod(); - if (calling_method->IsNative() || called_method->IsNative()) { - art::Locks::thread_list_lock_->ExclusiveUnlock(self); - return ERR(OPAQUE_FRAME); +class SetupMethodExitEvents { + public: + SetupMethodExitEvents(art::Thread* self, + EventHandler* event_handler, + jthread target) REQUIRES(!art::Locks::mutator_lock_, + !art::Locks::user_code_suspension_lock_, + !art::Locks::thread_list_lock_) + : self_(self), event_handler_(event_handler), target_(target) { + DCHECK(target != nullptr); + art::Locks::mutator_lock_->AssertNotHeld(self_); + art::Locks::user_code_suspension_lock_->AssertNotHeld(self_); + art::Locks::thread_list_lock_->AssertNotHeld(self_); + event_handler_->SetInternalEvent( + target_, ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue, JVMTI_ENABLE); + } + + ~SetupMethodExitEvents() REQUIRES(!art::Locks::mutator_lock_, + !art::Locks::user_code_suspension_lock_, + !art::Locks::thread_list_lock_) { + art::Locks::mutator_lock_->AssertNotHeld(self_); + art::Locks::user_code_suspension_lock_->AssertNotHeld(self_); + art::Locks::thread_list_lock_->AssertNotHeld(self_); + if (failed_) { + event_handler_->SetInternalEvent( + target_, ArtJvmtiEvent::kForceEarlyReturnUpdateReturnValue, JVMTI_DISABLE); + } } - // 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); + void NotifyFailure() { + failed_ = true; + } + + private: + art::Thread* self_; + EventHandler* event_handler_; + jthread target_; + bool failed_ = false; +}; + +template <typename T> +void AddDelayedMethodExitEvent(EventHandler* handler, art::ShadowFrame* frame, T value) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(art::Locks::user_code_suspension_lock_, art::Locks::thread_list_lock_); + +template <typename T> +void AddDelayedMethodExitEvent(EventHandler* handler, art::ShadowFrame* frame, T value) { + art::JValue val = art::JValue::FromPrimitive(value); + jvalue jval{ .j = val.GetJ() }; + handler->AddDelayedNonStandardExitEvent(frame, false, jval); +} + +template <> +void AddDelayedMethodExitEvent<std::nullptr_t>(EventHandler* handler, + art::ShadowFrame* frame, + std::nullptr_t null_val ATTRIBUTE_UNUSED) { + jvalue jval; + memset(&jval, 0, sizeof(jval)); + handler->AddDelayedNonStandardExitEvent(frame, false, jval); +} + +template <> +void AddDelayedMethodExitEvent<jobject>(EventHandler* handler, + art::ShadowFrame* frame, + jobject obj) { + jvalue jval{ .l = art::Thread::Current()->GetJniEnv()->NewGlobalRef(obj) }; + handler->AddDelayedNonStandardExitEvent(frame, true, jval); +} - CHECK_NE(called_shadow_frame, calling_shadow_frame) - << "Frames at different depths not different!"; +template <typename T> +bool ValidReturnType(art::Thread* self, art::ObjPtr<art::mirror::Class> return_type, T value) + REQUIRES_SHARED(art::Locks::mutator_lock_) + REQUIRES(art::Locks::user_code_suspension_lock_, art::Locks::thread_list_lock_); + +#define SIMPLE_VALID_RETURN_TYPE(type, ...) \ + template <> \ + bool ValidReturnType<type>(art::Thread * self ATTRIBUTE_UNUSED, \ + art::ObjPtr<art::mirror::Class> return_type, \ + type value ATTRIBUTE_UNUSED) { \ + static constexpr std::initializer_list<art::Primitive::Type> types{ __VA_ARGS__ }; \ + return std::find(types.begin(), types.end(), return_type->GetPrimitiveType()) != types.end(); \ + } + +SIMPLE_VALID_RETURN_TYPE(jlong, art::Primitive::kPrimLong); +SIMPLE_VALID_RETURN_TYPE(jfloat, art::Primitive::kPrimFloat); +SIMPLE_VALID_RETURN_TYPE(jdouble, art::Primitive::kPrimDouble); +SIMPLE_VALID_RETURN_TYPE(nullptr_t, art::Primitive::kPrimVoid); +SIMPLE_VALID_RETURN_TYPE(jint, + art::Primitive::kPrimInt, + art::Primitive::kPrimChar, + art::Primitive::kPrimBoolean, + art::Primitive::kPrimShort, + art::Primitive::kPrimByte); +#undef SIMPLE_VALID_RETURN_TYPE + +template <> +bool ValidReturnType<jobject>(art::Thread* self, + art::ObjPtr<art::mirror::Class> return_type, + jobject return_value) { + if (return_type->IsPrimitive()) { + return false; + } + if (return_value == nullptr) { + // Null can be used for anything. + return true; + } + return return_type->IsAssignableFrom(self->DecodeJObject(return_value)->GetClass()); +} + +} // namespace +jvmtiError StackUtil::PopFrame(jvmtiEnv* env, jthread thread) { + art::Thread* self = art::Thread::Current(); + NonStandardExitFrames<NonStandardExitType::kPopFrame> frames(self, env, thread); + if (frames.result_ != OK) { + art::Locks::thread_list_lock_->ExclusiveUnlock(self); + return frames.result_; + } // Tell the shadow-frame to return immediately and skip all exit events. - called_shadow_frame->SetForcePopFrame(true); - calling_shadow_frame->SetForceRetryInstruction(true); + frames.penultimate_frame_->SetForceRetryInstruction(true); + frames.final_frame_->SetForcePopFrame(true); + frames.final_frame_->SetSkipMethodExitEvents(true); + if (frames.created_final_frame_ || frames.created_penultimate_frame_) { + art::FunctionClosure fc([](art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_){ + DeoptManager::Get()->DeoptimizeThread(self); + }); + frames.target_->RequestSynchronousCheckpoint(&fc); + } else { + art::Locks::thread_list_lock_->ExclusiveUnlock(self); + } + return OK; +} - // 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) { - art::FunctionClosure fc([](art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) { +template <typename T> +jvmtiError +StackUtil::ForceEarlyReturn(jvmtiEnv* env, EventHandler* event_handler, jthread thread, T value) { + art::Thread* self = art::Thread::Current(); + // We don't want to use the null == current-thread idiom since for events (that we use internally + // to implement force-early-return) we instead have null == all threads. Instead just get the + // current jthread if needed. + ScopedLocalRef<jthread> cur_thread(self->GetJniEnv(), nullptr); + if (UNLIKELY(thread == nullptr)) { + art::ScopedObjectAccess soa(self); + cur_thread.reset(soa.AddLocalReference<jthread>(self->GetPeer())); + thread = cur_thread.get(); + } + // This sets up the exit events we implement early return using before we have the locks and + // thanks to destructor ordering will tear them down if something goes wrong. + SetupMethodExitEvents smee(self, event_handler, thread); + NonStandardExitFrames<NonStandardExitType::kForceReturn> frames(self, env, thread); + if (frames.result_ != OK) { + smee.NotifyFailure(); + art::Locks::thread_list_lock_->ExclusiveUnlock(self); + return frames.result_; + } else if (!ValidReturnType<T>( + self, frames.final_frame_->GetMethod()->ResolveReturnType(), value)) { + smee.NotifyFailure(); + art::Locks::thread_list_lock_->ExclusiveUnlock(self); + return ERR(TYPE_MISMATCH); + } else if (frames.final_frame_->GetForcePopFrame()) { + // TODO We should really support this. + smee.NotifyFailure(); + std::string thread_name; + frames.target_->GetThreadName(thread_name); + JVMTI_LOG(WARNING, env) << "PopFrame or force-return already pending on thread " << thread_name; + art::Locks::thread_list_lock_->ExclusiveUnlock(self); + return ERR(OPAQUE_FRAME); + } + // Tell the shadow-frame to return immediately and skip all exit events. + frames.final_frame_->SetForcePopFrame(true); + AddDelayedMethodExitEvent<T>(event_handler, frames.final_frame_, value); + if (frames.created_final_frame_ || frames.created_penultimate_frame_) { + art::FunctionClosure fc([](art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_){ DeoptManager::Get()->DeoptimizeThread(self); }); - target->RequestSynchronousCheckpoint(&fc); + frames.target_->RequestSynchronousCheckpoint(&fc); } else { art::Locks::thread_list_lock_->ExclusiveUnlock(self); } return OK; } +// Instantiate the ForceEarlyReturn templates. +template jvmtiError StackUtil::ForceEarlyReturn(jvmtiEnv*, EventHandler*, jthread, jint); +template jvmtiError StackUtil::ForceEarlyReturn(jvmtiEnv*, EventHandler*, jthread, jlong); +template jvmtiError StackUtil::ForceEarlyReturn(jvmtiEnv*, EventHandler*, jthread, jfloat); +template jvmtiError StackUtil::ForceEarlyReturn(jvmtiEnv*, EventHandler*, jthread, jdouble); +template jvmtiError StackUtil::ForceEarlyReturn(jvmtiEnv*, EventHandler*, jthread, jobject); +template jvmtiError StackUtil::ForceEarlyReturn(jvmtiEnv*, EventHandler*, jthread, nullptr_t); + } // namespace openjdkjvmti diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h index 55c4269086..918aa4ce90 100644 --- a/openjdkjvmti/ti_stack.h +++ b/openjdkjvmti/ti_stack.h @@ -37,6 +37,7 @@ #include "art_method.h" #include "base/mutex.h" +#include "events.h" #include "stack.h" namespace openjdkjvmti { @@ -83,6 +84,10 @@ class StackUtil { static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth); static jvmtiError PopFrame(jvmtiEnv* env, jthread thread); + + template <typename T> + static jvmtiError ForceEarlyReturn( + jvmtiEnv* env, EventHandler* event_handler, jthread thread, T value); }; struct FindFrameAtDepthVisitor : art::StackVisitor { diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc index 6c50a2039c..f2ae996b3a 100644 --- a/openjdkjvmti/ti_thread.cc +++ b/openjdkjvmti/ti_thread.cc @@ -229,6 +229,7 @@ bool ThreadUtil::GetNativeThread(jthread thread, const art::ScopedObjectAccessAlreadyRunnable& soa, /*out*/ art::Thread** thr, /*out*/ jvmtiError* err) { + art::ScopedExceptionStorage sse(soa.Self()); if (thread == nullptr) { *thr = art::Thread::Current(); return true; diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h index c5443bfb9f..5bf8a3fd14 100644 --- a/openjdkjvmti/ti_thread.h +++ b/openjdkjvmti/ti_thread.h @@ -39,6 +39,7 @@ #include "base/macros.h" #include "base/mutex.h" +#include "handle.h" #include "thread.h" namespace art { @@ -46,6 +47,9 @@ class ArtField; class ScopedObjectAccessAlreadyRunnable; class Thread; class Closure; +namespace mirror { +class Throwable; +} // namespace mirror } // namespace art namespace openjdkjvmti { diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h index a7491069fa..2f86fbcca6 100644 --- a/runtime/common_dex_operations.h +++ b/runtime/common_dex_operations.h @@ -179,7 +179,6 @@ ALWAYS_INLINE bool DoFieldPutCommon(Thread* self, // 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; } } diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 02fb5b6da4..914a7509b8 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -23,12 +23,14 @@ #include <set> #include <vector> +#include "android-base/macros.h" #include "android-base/stringprintf.h" #include "arch/context.h" #include "art_field-inl.h" #include "art_method-inl.h" #include "base/enums.h" +#include "base/memory_tool.h" #include "base/safe_map.h" #include "base/strlcpy.h" #include "base/time_utils.h" @@ -49,6 +51,7 @@ #include "gc/space/large_object_space.h" #include "gc/space/space-inl.h" #include "handle_scope-inl.h" +#include "instrumentation.h" #include "jdwp/jdwp_priv.h" #include "jdwp/object_registry-inl.h" #include "jni/jni_internal.h" @@ -179,7 +182,8 @@ class DebugInstrumentationListener final : public instrumentation::Instrumentati Handle<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - const JValue& return_value) + instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED, + JValue& return_value) override REQUIRES_SHARED(Locks::mutator_lock_) { if (method->IsNative()) { // TODO: post location events is a suspension point and native method entry stubs aren't. diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 5dbdf4151e..9971f4a32b 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -965,6 +965,7 @@ extern "C" uint64_t artQuickProxyInvokeHandler( soa.Decode<mirror::Object>(rcvr_jobj), proxy_method, 0, + {}, result); } return result.GetJ(); diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 984f9477bd..fc2143f11e 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -16,6 +16,8 @@ #include "instrumentation.h" +#include <functional> +#include <optional> #include <sstream> #include <android-base/logging.h> @@ -39,6 +41,7 @@ #include "jit/jit.h" #include "jit/jit_code_cache.h" #include "jvalue-inl.h" +#include "jvalue.h" #include "mirror/class-inl.h" #include "mirror/dex_cache.h" #include "mirror/object-inl.h" @@ -54,16 +57,20 @@ namespace instrumentation { constexpr bool kVerboseInstrumentation = false; -void InstrumentationListener::MethodExited(Thread* thread, - Handle<mirror::Object> this_object, - ArtMethod* method, - uint32_t dex_pc, - Handle<mirror::Object> return_value) { +void InstrumentationListener::MethodExited( + Thread* thread, + Handle<mirror::Object> this_object, + ArtMethod* method, + uint32_t dex_pc, + OptionalFrame frame, + MutableHandle<mirror::Object>& return_value) { DCHECK_EQ(method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetReturnTypePrimitive(), Primitive::kPrimNot); + const void* original_ret = return_value.Get(); JValue v; v.SetL(return_value.Get()); - MethodExited(thread, this_object, method, dex_pc, v); + MethodExited(thread, this_object, method, dex_pc, frame, v); + DCHECK(original_ret == v.GetL()) << "Return value changed"; } void InstrumentationListener::FieldWritten(Thread* thread, @@ -485,8 +492,9 @@ static void InstrumentationRestoreStack(Thread* thread, void* arg) !m->IsRuntimeMethod()) { // Create the method exit events. As the methods didn't really exit the result is 0. // We only do this if no debugger is attached to prevent from posting events twice. + JValue val; instrumentation_->MethodExitEvent(thread_, instrumentation_frame.this_object_, m, - GetDexPc(), JValue()); + GetDexPc(), OptionalFrame{}, val); } frames_removed_++; removed_stub = true; @@ -1167,29 +1175,46 @@ void Instrumentation::MethodEnterEventImpl(Thread* thread, } } +template <> void Instrumentation::MethodExitEventImpl(Thread* thread, ObjPtr<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - const JValue& return_value) const { + OptionalFrame frame, + MutableHandle<mirror::Object>& return_value) const { + if (HasMethodExitListeners()) { + Thread* self = Thread::Current(); + StackHandleScope<1> hs(self); + Handle<mirror::Object> thiz(hs.NewHandle(this_object)); + for (InstrumentationListener* listener : method_exit_listeners_) { + if (listener != nullptr) { + listener->MethodExited(thread, thiz, method, dex_pc, frame, return_value); + } + } + } +} + +template<> void Instrumentation::MethodExitEventImpl(Thread* thread, + ObjPtr<mirror::Object> this_object, + ArtMethod* method, + uint32_t dex_pc, + OptionalFrame frame, + JValue& return_value) const { if (HasMethodExitListeners()) { Thread* self = Thread::Current(); StackHandleScope<2> hs(self); Handle<mirror::Object> thiz(hs.NewHandle(this_object)); - if (method->GetInterfaceMethodIfProxy(kRuntimePointerSize) - ->GetReturnTypePrimitive() != Primitive::kPrimNot) { + if (method->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetReturnTypePrimitive() != + Primitive::kPrimNot) { for (InstrumentationListener* listener : method_exit_listeners_) { if (listener != nullptr) { - listener->MethodExited(thread, thiz, method, dex_pc, return_value); + listener->MethodExited(thread, thiz, method, dex_pc, frame, return_value); } } } else { - Handle<mirror::Object> ret(hs.NewHandle(return_value.GetL())); - for (InstrumentationListener* listener : method_exit_listeners_) { - if (listener != nullptr) { - listener->MethodExited(thread, thiz, method, dex_pc, ret); - } - } + MutableHandle<mirror::Object> ret(hs.NewHandle(return_value.GetL())); + MethodExitEventImpl(thread, thiz.Get(), method, dex_pc, frame, ret); + return_value.SetL(ret.Get()); } } } @@ -1518,7 +1543,8 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uint32_t dex_pc = dex::kDexNoIndex; if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) { ObjPtr<mirror::Object> this_object = instrumentation_frame.this_object_; - MethodExitEvent(self, this_object, instrumentation_frame.method_, dex_pc, return_value); + MethodExitEvent( + self, this_object, instrumentation_frame.method_, dex_pc, OptionalFrame{}, return_value); } // Deoptimize if the caller needs to continue execution in the interpreter. Do nothing if we get diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index fc64c49f7a..a7907c8842 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -17,10 +17,12 @@ #ifndef ART_RUNTIME_INSTRUMENTATION_H_ #define ART_RUNTIME_INSTRUMENTATION_H_ +#include <functional> #include <stdint.h> #include <list> #include <memory> #include <unordered_set> +#include <optional> #include "arch/instruction_set.h" #include "base/enums.h" @@ -60,6 +62,11 @@ enum InterpreterHandlerTable { // application's performance. static constexpr bool kDeoptimizeForAccurateMethodEntryExitListeners = true; +// an optional frame is either Some(const ShadowFrame& current_frame) or None depending on if the +// method being exited has a shadow-frame associed with the current stack frame. In cases where +// there is no shadow-frame associated with this stack frame this will be None. +using OptionalFrame = std::optional<std::reference_wrapper<const ShadowFrame>>; + // Instrumentation event listener API. Registered listeners will get the appropriate call back for // the events they are listening for. The call backs supply the thread, method and dex_pc the event // occurred upon. The thread may or may not be Thread::Current(). @@ -77,7 +84,8 @@ struct InstrumentationListener { Handle<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - Handle<mirror::Object> return_value) + OptionalFrame frame, + MutableHandle<mirror::Object>& return_value) REQUIRES_SHARED(Locks::mutator_lock_); // Call-back for when a method is exited. The implementor should either handler-ize the return @@ -87,7 +95,8 @@ struct InstrumentationListener { Handle<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - const JValue& return_value) + OptionalFrame frame, + JValue& return_value) REQUIRES_SHARED(Locks::mutator_lock_) = 0; // Call-back for when a method is popped due to an exception throw. A method will either cause a @@ -399,14 +408,16 @@ class Instrumentation { } // Inform listeners that a method has been exited. + template<typename T> void MethodExitEvent(Thread* thread, ObjPtr<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - const JValue& return_value) const + OptionalFrame frame, + T& return_value) const REQUIRES_SHARED(Locks::mutator_lock_) { if (UNLIKELY(HasMethodExitListeners())) { - MethodExitEventImpl(thread, this_object, method, dex_pc, return_value); + MethodExitEventImpl(thread, this_object, method, dex_pc, frame, return_value); } } @@ -583,11 +594,13 @@ class Instrumentation { ArtMethod* method, uint32_t dex_pc) const REQUIRES_SHARED(Locks::mutator_lock_); + template <typename T> void MethodExitEventImpl(Thread* thread, ObjPtr<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - const JValue& return_value) const + OptionalFrame frame, + T& return_value) const REQUIRES_SHARED(Locks::mutator_lock_); void DexPcMovedEventImpl(Thread* thread, ObjPtr<mirror::Object> this_object, diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc index cf5d3ed139..6284299855 100644 --- a/runtime/instrumentation_test.cc +++ b/runtime/instrumentation_test.cc @@ -16,6 +16,7 @@ #include "instrumentation.h" +#include "android-base/macros.h" #include "art_method-inl.h" #include "base/enums.h" #include "class_linker-inl.h" @@ -66,7 +67,8 @@ class TestInstrumentationListener final : public instrumentation::Instrumentatio Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, - Handle<mirror::Object> return_value ATTRIBUTE_UNUSED) + instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED, + MutableHandle<mirror::Object>& return_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_method_exit_object_event = true; } @@ -75,7 +77,8 @@ class TestInstrumentationListener final : public instrumentation::Instrumentatio Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, - const JValue& return_value ATTRIBUTE_UNUSED) + instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED, + JValue& return_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) { received_method_exit_event = true; } @@ -393,7 +396,7 @@ class InstrumentationTest : public CommonRuntimeTest { break; case instrumentation::Instrumentation::kMethodExited: { JValue value; - instr->MethodExitEvent(self, obj, method, dex_pc, value); + instr->MethodExitEvent(self, obj, method, dex_pc, {}, value); break; } case instrumentation::Instrumentation::kMethodUnwind: @@ -520,7 +523,8 @@ TEST_F(InstrumentationTest, MethodExitObjectEvent) { Runtime* const runtime = Runtime::Current(); ClassLinker* class_linker = runtime->GetClassLinker(); StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); + MutableHandle<mirror::ClassLoader> loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); ObjPtr<mirror::Class> klass = class_linker->FindClass(soa.Self(), "LInstrumentation;", loader); ASSERT_TRUE(klass != nullptr); ArtMethod* method = diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc index ce242a74c7..5f176db2f7 100644 --- a/runtime/interpreter/interpreter.cc +++ b/runtime/interpreter/interpreter.cc @@ -275,17 +275,38 @@ static inline JValue Execute( method, 0); if (UNLIKELY(shadow_frame.GetForcePopFrame())) { - // The caller will retry this invoke. Just return immediately without any value. + // The caller will retry this invoke or ignore the result. Just return immediately without + // any value. DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); - DCHECK(PrevFrameWillRetry(self, shadow_frame)); - return JValue(); + JValue ret = JValue(); + bool res = PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>( + self, + shadow_frame, + ret, + instrumentation, + accessor.InsSize(), + 0); + DCHECK(res) << "Expected to perform non-standard return!"; + return ret; } if (UNLIKELY(self->IsExceptionPending())) { instrumentation->MethodUnwindEvent(self, shadow_frame.GetThisObject(accessor.InsSize()), method, 0); - return JValue(); + JValue ret = JValue(); + if (UNLIKELY(shadow_frame.GetForcePopFrame())) { + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); + bool res = PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>( + self, + shadow_frame, + ret, + instrumentation, + accessor.InsSize(), + 0); + DCHECK(res) << "Expected to perform non-standard return!"; + } + return ret; } } diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 4e08e8c85b..9953743f04 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -24,6 +24,7 @@ #include "debugger.h" #include "dex/dex_file_types.h" #include "entrypoints/runtime_asm_entrypoints.h" +#include "handle.h" #include "intrinsics_enum.h" #include "jit/jit.h" #include "jvalue-inl.h" @@ -416,7 +417,6 @@ bool DoIPutQuick(const ShadowFrame& shadow_frame, const Instruction* inst, uint1 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; } } @@ -470,6 +470,57 @@ EXPLICIT_DO_IPUT_QUICK_ALL_TEMPLATE_DECL(Primitive::kPrimNot) // iput-objec #undef EXPLICIT_DO_IPUT_QUICK_ALL_TEMPLATE_DECL #undef EXPLICIT_DO_IPUT_QUICK_TEMPLATE_DECL +template <typename T> +bool SendMethodExitEvents(Thread* self, + const instrumentation::Instrumentation* instrumentation, + ShadowFrame& frame, + ObjPtr<mirror::Object> thiz, + ArtMethod* method, + uint32_t dex_pc, + T& result) { + bool had_event = false; + // We can get additional ForcePopFrame requests during handling of these events. We should + // respect these and send additional instrumentation events. + StackHandleScope<1> hs(self); + Handle<mirror::Object> h_thiz(hs.NewHandle(thiz)); + do { + frame.SetForcePopFrame(false); + if (UNLIKELY(instrumentation->HasMethodExitListeners() && !frame.GetSkipMethodExitEvents())) { + had_event = true; + instrumentation->MethodExitEvent( + self, h_thiz.Get(), method, dex_pc, instrumentation::OptionalFrame{ frame }, result); + } + // We don't send method-exit if it's a pop-frame. We still send frame_popped though. + if (UNLIKELY(frame.NeedsNotifyPop() && instrumentation->HasWatchedFramePopListeners())) { + had_event = true; + instrumentation->WatchedFramePopped(self, frame); + } + } while (UNLIKELY(frame.GetForcePopFrame())); + if (UNLIKELY(had_event)) { + return !self->IsExceptionPending(); + } else { + return true; + } +} + +template +bool SendMethodExitEvents(Thread* self, + const instrumentation::Instrumentation* instrumentation, + ShadowFrame& frame, + ObjPtr<mirror::Object> thiz, + ArtMethod* method, + uint32_t dex_pc, + MutableHandle<mirror::Object>& result); + +template +bool SendMethodExitEvents(Thread* self, + const instrumentation::Instrumentation* instrumentation, + ShadowFrame& frame, + ObjPtr<mirror::Object> thiz, + ArtMethod* method, + uint32_t dex_pc, + JValue& result); + // We execute any instrumentation events that are triggered by this exception and change the // shadow_frame's dex_pc to that of the exception handler if there is one in the current method. // Return true if we should continue executing in the current method and false if we need to go up @@ -501,6 +552,12 @@ bool MoveToExceptionHandler(Thread* self, if (instrumentation != nullptr) { if (shadow_frame.NeedsNotifyPop()) { instrumentation->WatchedFramePopped(self, shadow_frame); + 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 and to ensure we + // handle other types of non-standard-exits. + return true; + } } // Exception is not caught by the current method. We will unwind to the // caller. Notify any instrumentation listener. @@ -509,7 +566,7 @@ bool MoveToExceptionHandler(Thread* self, shadow_frame.GetMethod(), shadow_frame.GetDexPC()); } - return false; + return shadow_frame.GetForcePopFrame(); } else { shadow_frame.SetDexPC(found_dex_pc); if (instrumentation != nullptr && instrumentation->HasExceptionHandledListeners()) { diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 19da77dd3a..752965feff 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -17,6 +17,8 @@ #ifndef ART_RUNTIME_INTERPRETER_INTERPRETER_COMMON_H_ #define ART_RUNTIME_INTERPRETER_INTERPRETER_COMMON_H_ +#include "android-base/macros.h" +#include "instrumentation.h" #include "interpreter.h" #include "interpreter_intrinsics.h" @@ -59,6 +61,7 @@ #include "stack.h" #include "thread.h" #include "unstarted_runtime.h" +#include "verifier/method_verifier.h" #include "well_known_classes.h" namespace art { @@ -132,6 +135,96 @@ bool UseFastInterpreterToInterpreterInvoke(ArtMethod* method) NO_INLINE bool CheckStackOverflow(Thread* self, size_t frame_size) REQUIRES_SHARED(Locks::mutator_lock_); + +// Sends the normal method exit event. +// Returns true if the events succeeded and false if there is a pending exception. +template <typename T> bool SendMethodExitEvents( + Thread* self, + const instrumentation::Instrumentation* instrumentation, + ShadowFrame& frame, + ObjPtr<mirror::Object> thiz, + ArtMethod* method, + uint32_t dex_pc, + T& result) REQUIRES_SHARED(Locks::mutator_lock_); + +static inline ALWAYS_INLINE WARN_UNUSED bool +NeedsMethodExitEvent(const instrumentation::Instrumentation* ins) + REQUIRES_SHARED(Locks::mutator_lock_) { + return ins->HasMethodExitListeners() || ins->HasWatchedFramePopListeners(); +} + +// NO_INLINE so we won't bloat the interpreter with this very cold lock-release code. +template <bool kMonitorCounting> +static NO_INLINE void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame) + REQUIRES_SHARED(Locks::mutator_lock_) { + DCHECK(shadow_frame->GetForcePopFrame()); + // Unlock all monitors. + if (kMonitorCounting && shadow_frame->GetMethod()->MustCountLocks()) { + // Get the monitors from the shadow-frame monitor-count data. + shadow_frame->GetLockCountData().VisitMonitors( + [&](mirror::Object** obj) REQUIRES_SHARED(Locks::mutator_lock_) { + // Since we don't use the 'obj' pointer after the DoMonitorExit everything should be fine + // WRT suspension. + DoMonitorExit<kMonitorCounting>(self, shadow_frame, *obj); + }); + } else { + std::vector<verifier::MethodVerifier::DexLockInfo> locks; + verifier::MethodVerifier::FindLocksAtDexPc(shadow_frame->GetMethod(), + shadow_frame->GetDexPC(), + &locks, + Runtime::Current()->GetTargetSdkVersion()); + for (const auto& reg : locks) { + if (UNLIKELY(reg.dex_registers.empty())) { + LOG(ERROR) << "Unable to determine reference locked by " + << shadow_frame->GetMethod()->PrettyMethod() << " at pc " + << shadow_frame->GetDexPC(); + } else { + DoMonitorExit<kMonitorCounting>( + self, shadow_frame, shadow_frame->GetVRegReference(*reg.dex_registers.begin())); + } + } + } +} + +enum class MonitorState { + kNoMonitorsLocked, + kCountingMonitors, + kNormalMonitors, +}; + +template<MonitorState kMonitorState> +static inline ALWAYS_INLINE WARN_UNUSED bool PerformNonStandardReturn( + Thread* self, + ShadowFrame& frame, + JValue& result, + const instrumentation::Instrumentation* instrumentation, + uint16_t num_dex_inst, + uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) { + static constexpr bool kMonitorCounting = (kMonitorState == MonitorState::kCountingMonitors); + ObjPtr<mirror::Object> thiz(frame.GetThisObject(num_dex_inst)); + if (UNLIKELY(frame.GetForcePopFrame())) { + StackHandleScope<1> hs(self); + Handle<mirror::Object> h_thiz(hs.NewHandle(thiz)); + DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); + if (UNLIKELY(self->IsExceptionPending())) { + LOG(WARNING) << "Suppressing exception for non-standard method exit: " + << self->GetException()->Dump(); + self->ClearException(); + } + if (kMonitorState != MonitorState::kNoMonitorsLocked) { + UnlockHeldMonitors<kMonitorCounting>(self, &frame); + } + DoMonitorCheckOnExit<kMonitorCounting>(self, &frame); + result = JValue(); + if (UNLIKELY(NeedsMethodExitEvent(instrumentation))) { + SendMethodExitEvents( + self, instrumentation, frame, h_thiz.Get(), frame.GetMethod(), dex_pc, result); + } + return true; + } + return false; +} + // Handles all invoke-XXX/range instructions except for invoke-polymorphic[/range]. // Returns true on success, otherwise throws an exception and returns false. template<InvokeType type, bool is_range, bool do_access_check, bool is_mterp, bool is_quick = false> diff --git a/runtime/interpreter/interpreter_switch_impl-inl.h b/runtime/interpreter/interpreter_switch_impl-inl.h index a487cd6630..085c475783 100644 --- a/runtime/interpreter/interpreter_switch_impl-inl.h +++ b/runtime/interpreter/interpreter_switch_impl-inl.h @@ -33,6 +33,7 @@ #include "jvalue-inl.h" #include "mirror/string-alloc-inl.h" #include "mirror/throwable.h" +#include "monitor.h" #include "nth_caller_visitor.h" #include "safe_math.h" #include "shadow_frame-inl.h" @@ -54,56 +55,14 @@ namespace interpreter { template<bool do_access_check, bool transaction_active, Instruction::Format kFormat> class InstructionHandler { public: - template <bool kMonitorCounting> - static NO_INLINE void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame) - REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(shadow_frame->GetForcePopFrame()); - // Unlock all monitors. - if (kMonitorCounting && shadow_frame->GetMethod()->MustCountLocks()) { - // Get the monitors from the shadow-frame monitor-count data. - shadow_frame->GetLockCountData().VisitMonitors( - [&](mirror::Object** obj) REQUIRES_SHARED(Locks::mutator_lock_) { - // Since we don't use the 'obj' pointer after the DoMonitorExit everything should be fine - // WRT suspension. - DoMonitorExit<do_assignability_check>(self, shadow_frame, *obj); - }); - } else { - std::vector<verifier::MethodVerifier::DexLockInfo> locks; - verifier::MethodVerifier::FindLocksAtDexPc(shadow_frame->GetMethod(), - shadow_frame->GetDexPC(), - &locks, - Runtime::Current()->GetTargetSdkVersion()); - for (const auto& reg : locks) { - if (UNLIKELY(reg.dex_registers.empty())) { - LOG(ERROR) << "Unable to determine reference locked by " - << shadow_frame->GetMethod()->PrettyMethod() << " at pc " - << shadow_frame->GetDexPC(); - } else { - DoMonitorExit<do_assignability_check>( - self, shadow_frame, shadow_frame->GetVRegReference(*reg.dex_registers.begin())); - } - } - } - } - ALWAYS_INLINE WARN_UNUSED bool CheckForceReturn() REQUIRES_SHARED(Locks::mutator_lock_) { - if (UNLIKELY(shadow_frame.GetForcePopFrame())) { - DCHECK(PrevFrameWillRetry(self, shadow_frame)) - << "Pop frame forced without previous frame ready to retry instruction!"; - DCHECK(Runtime::Current()->AreNonStandardExitsEnabled()); - UnlockHeldMonitors<do_assignability_check>(self, &shadow_frame); - DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame); - 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. */ + if (PerformNonStandardReturn<kMonitorState>(self, + shadow_frame, + ctx->result, + instrumentation, + Accessor().InsSize(), + inst->GetDexPc(Insns()))) { exit_interpreter_loop = true; return false; } @@ -339,39 +298,6 @@ class InstructionHandler { } } - static bool NeedsMethodExitEvent(const instrumentation::Instrumentation* ins) - REQUIRES_SHARED(Locks::mutator_lock_) { - return ins->HasMethodExitListeners() || ins->HasWatchedFramePopListeners(); - } - - // Sends the normal method exit event. - // Returns true if the events succeeded and false if there is a pending exception. - NO_INLINE static bool SendMethodExitEvents( - Thread* self, - const instrumentation::Instrumentation* instrumentation, - const ShadowFrame& frame, - ObjPtr<mirror::Object> thiz, - ArtMethod* method, - uint32_t dex_pc, - const JValue& result) - REQUIRES_SHARED(Locks::mutator_lock_) { - bool had_event = false; - // 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, method, dex_pc, result); - } - if (UNLIKELY(frame.NeedsNotifyPop() && instrumentation->HasWatchedFramePopListeners())) { - had_event = true; - instrumentation->WatchedFramePopped(self, frame); - } - if (UNLIKELY(had_event)) { - return !self->IsExceptionPending(); - } else { - return true; - } - } - #define BRANCH_INSTRUMENTATION(offset) \ if (!BranchInstrumentation(offset)) { \ return false; \ @@ -700,6 +626,8 @@ class InstructionHandler { HANDLE_PENDING_EXCEPTION(); } } + StackHandleScope<1> hs(self); + MutableHandle<mirror::Object> h_result(hs.NewHandle(obj_result)); result.SetL(obj_result); if (UNLIKELY(NeedsMethodExitEvent(instrumentation) && !SendMethodExitEvents(self, @@ -708,13 +636,13 @@ class InstructionHandler { shadow_frame.GetThisObject(Accessor().InsSize()), shadow_frame.GetMethod(), inst->GetDexPc(Insns()), - result))) { + h_result))) { if (!HandlePendingExceptionWithInstrumentation(nullptr)) { return false; } } - // Re-load since it might have moved during the MethodExitEvent. - result.SetL(GetVRegReference(ref_idx)); + // Re-load since it might have moved or been replaced during the MethodExitEvent. + result.SetL(h_result.Get()); if (ctx->interpret_one_instruction) { /* Signal mterp to return to caller */ shadow_frame.SetDexPC(dex::kDexNoIndex); @@ -2198,6 +2126,8 @@ class InstructionHandler { private: static constexpr bool do_assignability_check = do_access_check; + static constexpr MonitorState kMonitorState = + do_assignability_check ? MonitorState::kCountingMonitors : MonitorState::kNormalMonitors; const CodeItemDataAccessor& Accessor() { return ctx->accessor; } const uint16_t* Insns() { return ctx->accessor.Insns(); } diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h index 3f6b729644..8981816432 100644 --- a/runtime/interpreter/shadow_frame.h +++ b/runtime/interpreter/shadow_frame.h @@ -57,6 +57,9 @@ class ShadowFrame { kForcePopFrame = 1 << 1, // We have been asked to re-execute the last instruction. kForceRetryInst = 1 << 2, + // Mark that we expect the next frame to retry the last instruction (used by instrumentation and + // debuggers to keep track of required events) + kSkipMethodExitEvents = 1 << 3, }; public: @@ -374,6 +377,14 @@ class ShadowFrame { UpdateFrameFlag(enable, FrameFlags::kForceRetryInst); } + bool GetSkipMethodExitEvents() const { + return GetFrameFlag(FrameFlags::kSkipMethodExitEvents); + } + + void SetSkipMethodExitEvents(bool enable) { + UpdateFrameFlag(enable, FrameFlags::kSkipMethodExitEvents); + } + void CheckConsistentVRegs() const { if (kIsDebugBuild) { // A shadow frame visible to GC requires the following rule: for a given vreg, diff --git a/runtime/thread.cc b/runtime/thread.cc index fbbcc90ff0..3e83f654b5 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2141,20 +2141,7 @@ void Thread::DumpJavaStack(std::ostream& os, bool check_suspended, bool dump_loc // assumption that there is no exception pending on entry. Thus, stash any pending exception. // Thread::Current() instead of this in case a thread is dumping the stack of another suspended // thread. - struct ScopedExceptionStorage { - ScopedExceptionStorage() : scope(Thread::Current()) { - exc = scope.NewHandle(scope.Self()->GetException()); - scope.Self()->ClearException(); - } - ~ScopedExceptionStorage() { - if (exc != nullptr) { - scope.Self()->SetException(exc.Get()); - } - } - StackHandleScope<1> scope; - Handle<mirror::Throwable> exc; - }; - ScopedExceptionStorage ses; + ScopedExceptionStorage ses(Thread::Current()); std::unique_ptr<Context> context(Context::Create()); StackDumpVisitor dumper(os, const_cast<Thread*>(this), context.get(), @@ -3578,10 +3565,6 @@ void Thread::QuickDeliverException() { 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"); } force_deopt = force_frame_pop || force_retry_instr; } @@ -4292,4 +4275,16 @@ bool Thread::IsSystemDaemon() const { WellKnownClasses::java_lang_Thread_systemDaemon)->GetBoolean(GetPeer()); } +ScopedExceptionStorage::ScopedExceptionStorage(art::Thread* self) + : self_(self), hs_(self_), excp_(hs_.NewHandle<art::mirror::Throwable>(self_->GetException())) { + self_->ClearException(); +} + +ScopedExceptionStorage::~ScopedExceptionStorage() { + CHECK(!self_->IsExceptionPending()) << self_; + if (!excp_.IsNull()) { + self_->SetException(excp_.Get()); + } +} + } // namespace art diff --git a/runtime/thread.h b/runtime/thread.h index c4a9bcf357..8fe9466112 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -33,6 +33,7 @@ #include "base/value_object.h" #include "entrypoints/jni/jni_entrypoints.h" #include "entrypoints/quick/quick_entrypoints.h" +#include "handle.h" #include "handle_scope.h" #include "interpreter/interpreter_cache.h" #include "jvalue.h" @@ -1896,6 +1897,18 @@ class ThreadLifecycleCallback { virtual void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; }; +// Store an exception from the thread and suppress it for the duration of this object. +class ScopedExceptionStorage { + public: + explicit ScopedExceptionStorage(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_); + ~ScopedExceptionStorage() REQUIRES_SHARED(Locks::mutator_lock_); + + private: + Thread* self_; + StackHandleScope<1> hs_; + Handle<mirror::Throwable> excp_; +}; + std::ostream& operator<<(std::ostream& os, const Thread& thread); std::ostream& operator<<(std::ostream& os, const StackedShadowFrameType& thread); diff --git a/runtime/trace.cc b/runtime/trace.cc index 0c31faa97e..faea146dcb 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -19,6 +19,7 @@ #include <sys/uio.h> #include <unistd.h> +#include "android-base/macros.h" #include "android-base/stringprintf.h" #include "art_method-inl.h" @@ -746,12 +747,16 @@ void Trace::MethodExited(Thread* thread, Handle<mirror::Object> this_object ATTRIBUTE_UNUSED, ArtMethod* method, uint32_t dex_pc ATTRIBUTE_UNUSED, - const JValue& return_value ATTRIBUTE_UNUSED) { + instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED, + JValue& return_value ATTRIBUTE_UNUSED) { uint32_t thread_clock_diff = 0; uint32_t wall_clock_diff = 0; ReadClocks(thread, &thread_clock_diff, &wall_clock_diff); - LogMethodTraceEvent(thread, method, instrumentation::Instrumentation::kMethodExited, - thread_clock_diff, wall_clock_diff); + LogMethodTraceEvent(thread, + method, + instrumentation::Instrumentation::kMethodExited, + thread_clock_diff, + wall_clock_diff); } void Trace::MethodUnwind(Thread* thread, diff --git a/runtime/trace.h b/runtime/trace.h index 567f6edb22..eccf157d2d 100644 --- a/runtime/trace.h +++ b/runtime/trace.h @@ -185,7 +185,8 @@ class Trace final : public instrumentation::InstrumentationListener { Handle<mirror::Object> this_object, ArtMethod* method, uint32_t dex_pc, - const JValue& return_value) + instrumentation::OptionalFrame frame, + JValue& return_value) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_) override; void MethodUnwind(Thread* thread, diff --git a/test/1968-force-early-return/expected.txt b/test/1968-force-early-return/expected.txt new file mode 100644 index 0000000000..bd3859068f --- /dev/null +++ b/test/1968-force-early-return/expected.txt @@ -0,0 +1,195 @@ +Test stopped using breakpoint +NORMAL RUN: Single call with no interference on (ID: 0) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 0) StandardTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 1) StandardTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 0 } +result for (ID: 1) StandardTestObject { cnt: 1 } is OveriddenReturnValue { id: 0 } +Test stopped using breakpoint with declared synchronized function +NORMAL RUN: Single call with no interference on (ID: 2) SynchronizedFunctionTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 2) SynchronizedFunctionTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 3) SynchronizedFunctionTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 1 } +result for (ID: 3) SynchronizedFunctionTestObject { cnt: 1 } is OveriddenReturnValue { id: 1 } +Test stopped using breakpoint with synchronized block +NORMAL RUN: Single call with no interference on (ID: 4) SynchronizedTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 4) SynchronizedTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 5) SynchronizedTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 2 } +result for (ID: 5) SynchronizedTestObject { cnt: 1 } is OveriddenReturnValue { id: 2 } +Test stopped on single step +NORMAL RUN: Single call with no interference on (ID: 6) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 6) StandardTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 7) StandardTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 3 } +result for (ID: 7) StandardTestObject { cnt: 1 } is OveriddenReturnValue { id: 3 } +Test stopped on field access +NORMAL RUN: Single call with no interference on (ID: 8) FieldBasedTestObject { TARGET_FIELD: 0 } +NORMAL RUN: result for (ID: 8) FieldBasedTestObject { TARGET_FIELD: 10 } is IntContainer { value: 10 } +Single call with force-early-return on (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0 } +Will force return of OveriddenReturnValue { id: 4 } +result for (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0 } is OveriddenReturnValue { id: 4 } +Test stopped on field modification +NORMAL RUN: Single call with no interference on (ID: 10) FieldBasedTestObject { TARGET_FIELD: 0 } +NORMAL RUN: result for (ID: 10) FieldBasedTestObject { TARGET_FIELD: 10 } is IntContainer { value: 10 } +Single call with force-early-return on (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0 } +Will force return of OveriddenReturnValue { id: 5 } +result for (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0 } is OveriddenReturnValue { id: 5 } +Test stopped during Method Exit of calledFunction +NORMAL RUN: Single call with no interference on (ID: 12) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 12) StandardTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 13) StandardTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 6 } +result for (ID: 13) StandardTestObject { cnt: 2 } is OveriddenReturnValue { id: 6 } +Test stopped during Method Enter of calledFunction +NORMAL RUN: Single call with no interference on (ID: 14) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 14) StandardTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 15) StandardTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 7 } +result for (ID: 15) StandardTestObject { cnt: 0 } is OveriddenReturnValue { id: 7 } +Test stopped during Method Exit due to exception thrown in same function +NORMAL RUN: Single call with no interference on (ID: 16) ExceptionOnceObject { cnt: 0, throwInSub: false } +Uncaught exception in thread Thread[Test1968 target thread - 16,5,main] - art.Test1968$ExceptionOnceObject$TestError: null + art.Test1968$ExceptionOnceObject.calledFunction(Test1968.java) + art.Test1968$AbstractTestObject.run(Test1968.java) + art.Test1968$2.run(Test1968.java) + java.lang.Thread.run(Thread.java) + +NORMAL RUN: result for (ID: 16) ExceptionOnceObject { cnt: 1, throwInSub: false } is null +Single call with force-early-return on (ID: 17) ExceptionOnceObject { cnt: 0, throwInSub: false } +Will force return of OveriddenReturnValue { id: 8 } +result for (ID: 17) ExceptionOnceObject { cnt: 1, throwInSub: false } is OveriddenReturnValue { id: 8 } +Test stopped during Method Exit due to exception thrown in subroutine +NORMAL RUN: Single call with no interference on (ID: 18) ExceptionOnceObject { cnt: 0, throwInSub: true } +Uncaught exception in thread Thread[Test1968 target thread - 18,5,main] - art.Test1968$ExceptionOnceObject$TestError: null + art.Test1968$ExceptionOnceObject.doThrow(Test1968.java) + art.Test1968$ExceptionOnceObject.calledFunction(Test1968.java) + art.Test1968$AbstractTestObject.run(Test1968.java) + art.Test1968$2.run(Test1968.java) + java.lang.Thread.run(Thread.java) + +NORMAL RUN: result for (ID: 18) ExceptionOnceObject { cnt: 1, throwInSub: true } is null +Single call with force-early-return on (ID: 19) ExceptionOnceObject { cnt: 0, throwInSub: true } +Will force return of OveriddenReturnValue { id: 9 } +result for (ID: 19) ExceptionOnceObject { cnt: 1, throwInSub: true } is OveriddenReturnValue { id: 9 } +Test stopped during notifyFramePop with exception on pop of calledFunction +NORMAL RUN: Single call with no interference on (ID: 20) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1968$ExceptionThrowTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 20) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is null +Single call with force-early-return on (ID: 21) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of OveriddenReturnValue { id: 10 } +result for (ID: 21) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is OveriddenReturnValue { id: 10 } +Test stopped during notifyFramePop with exception on pop of doThrow +NORMAL RUN: Single call with no interference on (ID: 22) ExceptionCatchTestObject { cnt: 0 } +art.Test1968$ExceptionCatchTestObject$TestError caught in called function. +NORMAL RUN: result for (ID: 22) ExceptionCatchTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 23) ExceptionCatchTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 11 } +result for (ID: 23) ExceptionCatchTestObject { cnt: 101 } is IntContainer { value: 1 } +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +NORMAL RUN: Single call with no interference on (ID: 24) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1968$ExceptionThrowTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 24) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } is IntContainer { value: 11 } +Single call with force-early-return on (ID: 25) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of OveriddenReturnValue { id: 12 } +result for (ID: 25) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } is OveriddenReturnValue { id: 12 } +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +NORMAL RUN: Single call with no interference on (ID: 26) ExceptionCatchTestObject { cnt: 0 } +art.Test1968$ExceptionCatchTestObject$TestError caught in called function. +NORMAL RUN: result for (ID: 26) ExceptionCatchTestObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 27) ExceptionCatchTestObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 13 } +result for (ID: 27) ExceptionCatchTestObject { cnt: 1 } is OveriddenReturnValue { id: 13 } +Test stopped during Exception event of calledFunction (catch in calling function) +NORMAL RUN: Single call with no interference on (ID: 28) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1968$ExceptionThrowTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 28) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is null +Single call with force-early-return on (ID: 29) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of OveriddenReturnValue { id: 14 } +result for (ID: 29) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is OveriddenReturnValue { id: 14 } +Test stopped during Exception event of calledFunction (catch in called function) +NORMAL RUN: Single call with no interference on (ID: 30) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1968$ExceptionThrowTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 30) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } is IntContainer { value: 11 } +Single call with force-early-return on (ID: 31) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of OveriddenReturnValue { id: 15 } +result for (ID: 31) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } is OveriddenReturnValue { id: 15 } +Test stopped during Exception event of calledFunction (catch in parent of calling function) +NORMAL RUN: Single call with no interference on (ID: 32) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +art.Test1968$ExceptionThrowFarTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 32) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } is null +Single call with force-early-return on (ID: 33) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +Will force return of OveriddenReturnValue { id: 16 } +result for (ID: 33) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } is OveriddenReturnValue { id: 16 } +Test stopped during Exception event of calledFunction (catch in called function) +NORMAL RUN: Single call with no interference on (ID: 34) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +art.Test1968$ExceptionThrowFarTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 34) ExceptionThrowFarTestObject { cnt: 111, baseCnt: 2 } is IntContainer { value: 101 } +Single call with force-early-return on (ID: 35) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +Will force return of OveriddenReturnValue { id: 17 } +result for (ID: 35) ExceptionThrowFarTestObject { cnt: 101, baseCnt: 2 } is OveriddenReturnValue { id: 17 } +Test stopped during random Suspend. +NORMAL RUN: Single call with no interference on (ID: 36) SuspendSuddenlyObject { cnt: 0, spun: false } +NORMAL RUN: result for (ID: 36) SuspendSuddenlyObject { cnt: 2, spun: true } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 37) SuspendSuddenlyObject { cnt: 0, spun: false } +Will force return of OveriddenReturnValue { id: 18 } +result for (ID: 37) SuspendSuddenlyObject { cnt: 1, spun: true } is OveriddenReturnValue { id: 18 } +Test stopped during a native method fails +NORMAL RUN: Single call with no interference on (ID: 38) NativeCalledObject { cnt: 0 } +NORMAL RUN: result for (ID: 38) NativeCalledObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 39) NativeCalledObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 19 } +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.NonStandardExit.forceEarlyReturnObject(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1968$TestSuspender.performForceReturn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTests(Test1968.java) + <Additional frames hidden> + +result for (ID: 39) NativeCalledObject { cnt: 2 } is IntContainer { value: 1 } +Test stopped in a method called by native succeeds +NORMAL RUN: Single call with no interference on (ID: 40) NativeCallerObject { cnt: 0 } +NORMAL RUN: result for (ID: 40) NativeCallerObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 41) NativeCallerObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 20 } +result for (ID: 41) NativeCallerObject { cnt: 2 } is OveriddenReturnValue { id: 20 } +Test stopped in a static method +NORMAL RUN: Single call with no interference on (ID: 42) StaticMethodObject { cnt: 0 } +NORMAL RUN: result for (ID: 42) StaticMethodObject { cnt: 2 } is IntContainer { value: 1 } +Single call with force-early-return on (ID: 43) StaticMethodObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 21 } +result for (ID: 43) StaticMethodObject { cnt: 1 } is OveriddenReturnValue { id: 21 } +Test force-return of void function fails! +NORMAL RUN: Single call with no interference on (ID: 44) BadForceVoidObject { cnt: 0 } +NORMAL RUN: result for (ID: 44) BadForceVoidObject { cnt: 2 } is null +Single call with force-early-return on (ID: 45) BadForceVoidObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 22 } +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnObject(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1968$TestSuspender.performForceReturn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTests(Test1968.java) + <Additional frames hidden> + +result for (ID: 45) BadForceVoidObject { cnt: 2 } is null +Test force-return of int function fails! +NORMAL RUN: Single call with no interference on (ID: 46) BadForceIntObject { cnt: 0 } +NORMAL RUN: result for (ID: 46) BadForceIntObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 47) BadForceIntObject { cnt: 0 } +Will force return of OveriddenReturnValue { id: 23 } +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnObject(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1968$TestSuspender.performForceReturn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTestOn(Test1968.java) + art.Test1968.runTests(Test1968.java) + <Additional frames hidden> + +result for (ID: 47) BadForceIntObject { cnt: 2 } is 1 diff --git a/test/1968-force-early-return/force_early_return.cc b/test/1968-force-early-return/force_early_return.cc new file mode 100644 index 0000000000..674216501a --- /dev/null +++ b/test/1968-force-early-return/force_early_return.cc @@ -0,0 +1,82 @@ +/* + * 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" + +#include "suspend_event_helper.h" + +namespace art { +namespace Test1968ForceEarlyReturn { + +extern "C" JNIEXPORT +jobject JNICALL Java_art_Test1968_00024NativeCalledObject_calledFunction( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(4); + jclass klass = env->GetObjectClass(thiz); + jfieldID cnt = env->GetFieldID(klass, "cnt", "I"); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + jclass int_container_klass = env->FindClass("art/Test1968$IntContainer"); + jmethodID int_cont_new = env->GetMethodID(int_container_klass, "<init>", "(I)V"); + jobject res = env->NewObject(int_container_klass, int_cont_new, env->GetIntField(thiz, cnt)); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + void *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(/* thread */ nullptr, + reinterpret_cast<void**>(&data)))) { + env->PopLocalFrame(nullptr); + return nullptr; + } + if (data != nullptr) { + art::common_suspend_event::PerformSuspension(jvmti_env, env); + } + return env->PopLocalFrame(res); +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1968_00024NativeCallerObject_run( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(1); + jclass klass = env->GetObjectClass(thiz); + jfieldID ret = env->GetFieldID(klass, "returnValue", "Ljava/lang/Object;"); + jmethodID called = env->GetMethodID(klass, "calledFunction", "()Ljava/lang/Object;"); + env->SetObjectField(thiz, ret, env->CallObjectMethod(thiz, called)); + env->PopLocalFrame(nullptr); +} + +} // namespace Test1968ForceEarlyReturn +} // namespace art + diff --git a/test/1968-force-early-return/info.txt b/test/1968-force-early-return/info.txt new file mode 100644 index 0000000000..621d881dce --- /dev/null +++ b/test/1968-force-early-return/info.txt @@ -0,0 +1,4 @@ +Test JVMTI ForceEarlyReturnObject functionality + +Checks that we can call the ForceEarlyReturn functions successfully and force +returns of objects. It also checks some of the basic error modes. diff --git a/test/1968-force-early-return/run b/test/1968-force-early-return/run new file mode 100755 index 0000000000..d16d4e6091 --- /dev/null +++ b/test/1968-force-early-return/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/1968-force-early-return/src/Main.java b/test/1968-force-early-return/src/Main.java new file mode 100644 index 0000000000..2aa26bf590 --- /dev/null +++ b/test/1968-force-early-return/src/Main.java @@ -0,0 +1,22 @@ +/* + * 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.Test1968.run(); + } +} diff --git a/test/1968-force-early-return/src/art/Breakpoint.java b/test/1968-force-early-return/src/art/Breakpoint.java new file mode 120000 index 0000000000..3673916cc6 --- /dev/null +++ b/test/1968-force-early-return/src/art/Breakpoint.java @@ -0,0 +1 @@ +../../../jvmti-common/Breakpoint.java
\ No newline at end of file diff --git a/test/1968-force-early-return/src/art/NonStandardExit.java b/test/1968-force-early-return/src/art/NonStandardExit.java new file mode 120000 index 0000000000..d542a3caa7 --- /dev/null +++ b/test/1968-force-early-return/src/art/NonStandardExit.java @@ -0,0 +1 @@ +../../../jvmti-common/NonStandardExit.java
\ No newline at end of file diff --git a/test/1968-force-early-return/src/art/StackTrace.java b/test/1968-force-early-return/src/art/StackTrace.java new file mode 120000 index 0000000000..e1a08aadbd --- /dev/null +++ b/test/1968-force-early-return/src/art/StackTrace.java @@ -0,0 +1 @@ +../../../jvmti-common/StackTrace.java
\ No newline at end of file diff --git a/test/1968-force-early-return/src/art/SuspendEvents.java b/test/1968-force-early-return/src/art/SuspendEvents.java new file mode 120000 index 0000000000..f7a5f7e327 --- /dev/null +++ b/test/1968-force-early-return/src/art/SuspendEvents.java @@ -0,0 +1 @@ +../../../jvmti-common/SuspendEvents.java
\ No newline at end of file diff --git a/test/1968-force-early-return/src/art/Suspension.java b/test/1968-force-early-return/src/art/Suspension.java new file mode 120000 index 0000000000..bcef96f69d --- /dev/null +++ b/test/1968-force-early-return/src/art/Suspension.java @@ -0,0 +1 @@ +../../../jvmti-common/Suspension.java
\ No newline at end of file diff --git a/test/1968-force-early-return/src/art/Test1968.java b/test/1968-force-early-return/src/art/Test1968.java new file mode 100644 index 0000000000..a6aea86f27 --- /dev/null +++ b/test/1968-force-early-return/src/art/Test1968.java @@ -0,0 +1,903 @@ +/* + * 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 static art.SuspendEvents.setupFieldSuspendFor; +import static art.SuspendEvents.setupSuspendBreakpointFor; +import static art.SuspendEvents.setupSuspendExceptionEvent; +import static art.SuspendEvents.setupSuspendMethodEvent; +import static art.SuspendEvents.setupSuspendPopFrameEvent; +import static art.SuspendEvents.setupSuspendSingleStepAt; +import static art.SuspendEvents.setupTest; +import static art.SuspendEvents.waitForSuspendHit; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class Test1968 { + public static final boolean PRINT_STACK_TRACE = false; + public static long OVERRIDE_ID = 0; + + public static final class OveriddenReturnValue { + public final Thread target; + public final Thread.State state; + public final StackTraceElement stack[]; + public final long id; + + public OveriddenReturnValue(Thread thr) { + target = thr; + state = thr.getState(); + stack = thr.getStackTrace(); + id = OVERRIDE_ID++; + } + + public String toString() { + String stackTrace = + PRINT_STACK_TRACE + ? ",\n\tthread: " + + target.toString() + + ",\n\tstate: " + + state + + ",\n\tstack:\n" + + safeDumpStackTrace(stack, "\t\t") + + ",\n\t" + : ""; + return "OveriddenReturnValue { id: " + id + stackTrace + " }"; + } + } + + // Returns a value to be used for the return value of the given thread. + public static Object getOveriddenReturnValue(Thread thr) { + return new OveriddenReturnValue(thr); + } + + public static void doNothing() {} + + public interface TestRunnable extends Runnable { + public Object getReturnValue(); + } + + public static interface TestSuspender { + public void setupForceReturnRun(Thread thr); + + public void waitForSuspend(Thread thr); + + public void cleanup(Thread thr); + + public default void performForceReturn(Thread thr) { + Object ret = getOveriddenReturnValue(thr); + System.out.println("Will force return of " + ret); + NonStandardExit.forceEarlyReturn(thr, ret); + } + + public default void setupNormalRun(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 setupForceReturnRun(Thread thr) { + setup.run(thr); + } + + public void waitForSuspend(Thread thr) { + waitForSuspendHit(thr); + } + + public void cleanup(Thread thr) { + clean.run(thr); + } + }; + } + + public void runTestOn(Supplier<TestRunnable> testObj, ThreadRunnable su, ThreadRunnable cl) + throws Exception { + runTestOn(testObj, makeSuspend(su, cl)); + } + + private static void SafePrintStackTrace(StackTraceElement st[]) { + System.out.println(safeDumpStackTrace(st, "\t")); + } + + private static String safeDumpStackTrace(StackTraceElement st[], String prefix) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream os = new PrintStream(baos); + for (StackTraceElement e : st) { + os.println( + prefix + + e.getClassName() + + "." + + e.getMethodName() + + "(" + + (e.isNativeMethod() ? "Native Method" : e.getFileName()) + + ")"); + if (e.getClassName().equals("art.Test1968") && e.getMethodName().equals("runTests")) { + os.println(prefix + "<Additional frames hidden>"); + break; + } + } + os.flush(); + return baos.toString(); + } + + static long ID_COUNTER = 0; + + public TestRunnable Id(final TestRunnable tr) { + final long my_id = ID_COUNTER++; + return new TestRunnable() { + public void run() { + tr.run(); + } + + public Object getReturnValue() { + return tr.getReturnValue(); + } + + public String toString() { + return "(ID: " + my_id + ") " + tr.toString(); + } + }; + } + + public static long THREAD_COUNT = 0; + + public Thread mkThread(Runnable r) { + Thread t = new Thread(r, "Test1968 target thread - " + THREAD_COUNT++); + t.setUncaughtExceptionHandler( + (thr, e) -> { + System.out.println( + "Uncaught exception in thread " + + thr + + " - " + + e.getClass().getName() + + ": " + + e.getLocalizedMessage()); + SafePrintStackTrace(e.getStackTrace()); + }); + return t; + } + + final class TestConfig { + public final TestRunnable testObj; + public final TestSuspender suspender; + + public TestConfig(TestRunnable obj, TestSuspender su) { + this.testObj = obj; + this.suspender = su; + } + } + + public void runTestOn(Supplier<TestRunnable> testObjGen, TestSuspender su) throws Exception { + runTestOn(() -> new TestConfig(testObjGen.get(), su)); + } + + public void runTestOn(Supplier<TestConfig> config) throws Exception { + TestConfig normal_config = config.get(); + TestRunnable normal_run = Id(normal_config.testObj); + try { + System.out.println("NORMAL RUN: Single call with no interference on " + normal_run); + Thread normal_thread = mkThread(normal_run); + normal_config.suspender.setupNormalRun(normal_thread); + normal_thread.start(); + normal_thread.join(); + System.out.println( + "NORMAL RUN: result for " + normal_run + " is " + normal_run.getReturnValue()); + } catch (Exception e) { + System.out.println("NORMAL RUN: Ended with exception for " + normal_run + "!"); + e.printStackTrace(System.out); + } + + TestConfig force_return_config = config.get(); + TestRunnable testObj = Id(force_return_config.testObj); + TestSuspender su = force_return_config.suspender; + System.out.println("Single call with force-early-return on " + testObj); + 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 = + mkThread( + () -> { + 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.setupForceReturnRun(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. + su.performForceReturn(thr); + } catch (Exception e) { + System.out.println("Failed to force-return 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 for " + testObj + " is " + testObj.getReturnValue()); + } + + public abstract static class AbstractTestObject implements TestRunnable { + private Object resultObject; + + public AbstractTestObject() { + resultObject = null; + } + + public Object getReturnValue() { + return resultObject; + } + + public void run() { + // This function should have it's return-value replaced by force-early-return. + resultObject = calledFunction(); + } + + public abstract Object calledFunction(); + } + + public static class IntContainer { + private final int value; + + public IntContainer(int i) { + value = i; + } + + public String toString() { + return "IntContainer { value: " + value + " }"; + } + } + + public static class FieldBasedTestObject extends AbstractTestObject implements Runnable { + public int TARGET_FIELD; + + public FieldBasedTestObject() { + super(); + TARGET_FIELD = 0; + } + + public Object calledFunction() { + // We put a watchpoint here and force-early-return when we are at it. + TARGET_FIELD += 10; + return new IntContainer(TARGET_FIELD); + } + + public String toString() { + return "FieldBasedTestObject { TARGET_FIELD: " + TARGET_FIELD + " }"; + } + } + + public static class StandardTestObject extends AbstractTestObject implements Runnable { + public int cnt; + + public StandardTestObject() { + super(); + cnt = 0; + } + + public Object calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and PopFrame when we are at it. + Object result = new IntContainer(cnt); // line +2 + cnt++; // line +3 + return result; // line +4 + } + + 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 Object calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and PopFrame when we are at it. + Object result = new IntContainer(cnt); // line +2 + cnt++; // line +3 + return result; + } + + public String toString() { + return "SynchronizedFunctionTestObject { cnt: " + cnt + " }"; + } + } + + public static class SynchronizedTestObject extends AbstractTestObject implements Runnable { + public final Object lock; + public int cnt; + + public SynchronizedTestObject() { + this(new Object()); + } + + public SynchronizedTestObject(Object lock) { + super(); + this.lock = lock; + cnt = 0; + } + + public Object calledFunction() { + synchronized (lock) { // line +0 + cnt++; // line +1 + // We put a breakpoint here and PopFrame when we are at it. + Object result = new IntContainer(cnt); // line +3 + cnt++; // line +4 + return result; // line +5 + } + } + + 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 Object calledFunction() { + cnt++; + Object result = new IntContainer(cnt); + try { + doThrow(); + cnt += 100; + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in called function."); + cnt++; + } + return result; + } + + public Object 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 Object result; + + public ExceptionThrowFarTestObject(boolean catchInCalled) { + super(); + cnt = 0; + baseCallCnt = 0; + this.catchInCalled = catchInCalled; + } + + public void run() { + baseCallCnt++; + try { + result = callingFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + baseCallCnt++; + } + + public Object callingFunction() { + return calledFunction(); + } + + public Object calledFunction() { + cnt++; + if (catchInCalled) { + try { + cnt += 100; + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + Object result = new IntContainer(cnt); + cnt += 10; + return result; + } + } else { + cnt++; + throw new TestError(); // We put a watch here. + } + } + + public String toString() { + return "ExceptionThrowFarTestObject { cnt: " + cnt + ", baseCnt: " + baseCallCnt + " }"; + } + + @Override + public Object getReturnValue() { + return result; + } + } + + 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 Object calledFunction() { + cnt++; + if (cnt == 1) { + if (throwInSub) { + return doThrow(); + } else { + throw new TestError(); + } + } + return new IntContainer(cnt++); + } + + public Object 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 Object getReturnValue() { + return result; + } + + public int cnt; + public int baseCallCnt; + public final boolean catchInCalled; + public Object result; + + public ExceptionThrowTestObject(boolean catchInCalled) { + super(); + cnt = 0; + baseCallCnt = 0; + this.catchInCalled = catchInCalled; + } + + public void run() { + baseCallCnt++; + try { + result = calledFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + baseCallCnt++; + } + + public Object calledFunction() { + cnt++; + if (catchInCalled) { + try { + cnt += 10; + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + Object result = new IntContainer(cnt); + cnt += 100; + return result; + } + } else { + cnt += 1; + throw new TestError(); // We put a watch here. + } + } + + public String toString() { + return "ExceptionThrowTestObject { cnt: " + cnt + ", baseCnt: " + baseCallCnt + " }"; + } + } + + public static class NativeCalledObject extends AbstractTestObject { + public int cnt = 0; + + public native Object calledFunction(); + + public String toString() { + return "NativeCalledObject { cnt: " + cnt + " }"; + } + } + + public static class NativeCallerObject implements TestRunnable { + public Object returnValue = null; + public int cnt = 0; + + public Object getReturnValue() { + return returnValue; + } + + public native void run(); + + public Object calledFunction() { + cnt++; + // We will stop using a MethodExit event. + Object res = new IntContainer(cnt); + cnt++; + return res; + } + + public String toString() { + return "NativeCallerObject { cnt: " + cnt + " }"; + } + } + + public static class StaticMethodObject implements TestRunnable { + public int cnt = 0; + public Object result = null; + public Object getReturnValue() { + return result; + } + + public static Object calledFunction(Supplier<Object> incr) { + Object res = incr.get(); // line +0 + // We put a breakpoint here to force the return. + doNothing(); // line +2 + incr.get(); // line +3 + return res; // line +4 + } + + public void run() { + result = calledFunction(() -> new IntContainer(++cnt)); + } + + public String toString() { + return "StaticMethodObject { cnt: " + cnt + " }"; + } + } + + public static class SuspendSuddenlyObject extends AbstractTestObject { + public volatile boolean should_spin = true; + public volatile boolean is_spinning = false; + public int cnt = 0; + + public Object calledFunction() { + cnt++; + do { + is_spinning = true; + } while (should_spin); + return new IntContainer(cnt++); + } + + public String toString() { + return "SuspendSuddenlyObject { cnt: " + cnt + ", spun: " + is_spinning + " }"; + } + } + + public static class BadForceVoidObject implements TestRunnable { + public int cnt = 0; + public Object getReturnValue() { + return null; + } + public void run() { + incrCnt(); + } + public void incrCnt() { + ++cnt; // line +0 + // We set a breakpoint here and try to force-early-return. + doNothing(); // line +2 + ++cnt; // line +3 + } + public String toString() { + return "BadForceVoidObject { cnt: " + cnt + " }"; + } + } + + public static class BadForceIntObject implements TestRunnable { + public int cnt = 0; + public int result = 0; + public Object getReturnValue() { + return Integer.valueOf(result); + } + public void run() { + result = incrCnt(); + } + public int incrCnt() { + ++cnt; // line +0 + // We set a breakpoint here and try to force-early-return. + int res = cnt; // line +2 + ++cnt; // line +3 + return res; + } + public String toString() { + return "BadForceIntObject { cnt: " + cnt + " }"; + } + } + + public static void run() throws Exception { + new Test1968((x) -> {}).runTests(); + } + + public Test1968(Consumer<TestRunnable> preTest) { + this.preTest = preTest; + } + + private Consumer<TestRunnable> preTest; + + public static void no_runTestOn(Supplier<Object> a, ThreadRunnable b, ThreadRunnable c) {} + + public void runTests() throws Exception { + setupTest(); + + final Method calledFunction = StandardTestObject.class.getDeclaredMethod("calledFunction"); + // 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( + StandardTestObject::new, + (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr), + SuspendEvents::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( + SynchronizedFunctionTestObject::new, + (thr) -> setupSuspendBreakpointFor(syncFunctionCalledFunction, syncFunctionLoc, thr), + SuspendEvents::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"); + final Object lockObj = new Object(); + runTestOn( + () -> new SynchronizedTestObject(lockObj), + (thr) -> setupSuspendBreakpointFor(syncCalledFunction, syncLoc, thr), + SuspendEvents::clearSuspendBreakpointFor); + // Make sure we can still lock the object. + synchronized (lockObj) { } + + System.out.println("Test stopped on single step"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendSingleStepAt(calledFunction, loc, thr), + SuspendEvents::clearSuspendSingleStepFor); + + final Field target_field = FieldBasedTestObject.class.getDeclaredField("TARGET_FIELD"); + System.out.println("Test stopped on field access"); + runTestOn( + FieldBasedTestObject::new, + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, true, thr), + SuspendEvents::clearFieldSuspendFor); + + System.out.println("Test stopped on field modification"); + runTestOn( + FieldBasedTestObject::new, + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, false, thr), + SuspendEvents::clearFieldSuspendFor); + + System.out.println("Test stopped during Method Exit of calledFunction"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendMethodEvent(calledFunction, /* enter */ false, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test stopped during Method Enter of calledFunction"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendMethodEvent(calledFunction, /* enter */ true, thr), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::clearSuspendMethodEvent); + + 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), + SuspendEvents::clearSuspendPopFrameEvent); + + final Method exceptionCatchThrowMethod = + ExceptionCatchTestObject.class.getDeclaredMethod("doThrow"); + System.out.println("Test stopped during notifyFramePop with exception on pop of doThrow"); + runTestOn( + ExceptionCatchTestObject::new, + (thr) -> setupSuspendPopFrameEvent(0, exceptionCatchThrowMethod, thr), + SuspendEvents::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), + SuspendEvents::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( + ExceptionCatchTestObject::new, + (thr) -> setupSuspendExceptionEvent(exceptionCatchCalledMethod, /* catch */ true, thr), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::clearSuspendExceptionEvent); + + System.out.println("Test stopped during random Suspend."); + runTestOn(() -> { + final SuspendSuddenlyObject sso = new SuspendSuddenlyObject(); + return new TestConfig(sso, new TestSuspender() { + public void setupForceReturnRun(Thread thr) { } + public void setupNormalRun(Thread thr) { + sso.should_spin = false; + } + + public void waitForSuspend(Thread thr) { + while (!sso.is_spinning) { } + Suspension.suspend(thr); + } + + public void cleanup(Thread thr) { } + }); + }); + + System.out.println("Test stopped during a native method fails"); + runTestOn( + NativeCalledObject::new, + SuspendEvents::setupWaitForNativeCall, + SuspendEvents::clearWaitForNativeCall); + + System.out.println("Test stopped in a method called by native succeeds"); + final Method nativeCallerMethod = NativeCallerObject.class.getDeclaredMethod("calledFunction"); + runTestOn( + NativeCallerObject::new, + (thr) -> setupSuspendMethodEvent(nativeCallerMethod, /* enter */ false, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test stopped in a static method"); + final Method staticCalledMethod = StaticMethodObject.class.getDeclaredMethod("calledFunction", Supplier.class); + final int staticFunctionLine= Breakpoint.locationToLine(staticCalledMethod, 0) + 2; + final long staticFunctionLoc = Breakpoint.lineToLocation(staticCalledMethod, staticFunctionLine); + runTestOn( + StaticMethodObject::new, + (thr) -> setupSuspendBreakpointFor(staticCalledMethod, staticFunctionLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test force-return of void function fails!"); + final Method voidFunction = BadForceVoidObject.class.getDeclaredMethod("incrCnt"); + final int voidLine = Breakpoint.locationToLine(voidFunction, 0) + 2; + final long voidLoc = Breakpoint.lineToLocation(voidFunction, voidLine); + runTestOn( + BadForceVoidObject::new, + (thr) -> setupSuspendBreakpointFor(voidFunction, voidLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test force-return of int function fails!"); + final Method intFunction = BadForceIntObject.class.getDeclaredMethod("incrCnt"); + final int intLine = Breakpoint.locationToLine(intFunction, 0) + 2; + final long intLoc = Breakpoint.lineToLocation(intFunction, intLine); + runTestOn( + BadForceIntObject::new, + (thr) -> setupSuspendBreakpointFor(intFunction, intLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + } +} diff --git a/test/1969-force-early-return-void/check b/test/1969-force-early-return-void/check new file mode 100755 index 0000000000..d552272bca --- /dev/null +++ b/test/1969-force-early-return-void/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/1969-force-early-return-void/class-loading-expected.patch b/test/1969-force-early-return-void/class-loading-expected.patch new file mode 100644 index 0000000000..5e13595eb8 --- /dev/null +++ b/test/1969-force-early-return-void/class-loading-expected.patch @@ -0,0 +1,35 @@ +178a179,212 +> Test stopped during class-load. +> NORMAL RUN: Single call with no interference on (ID: 46) ClassLoadObject { cnt: 0, curClass: 0} +> TC0.foo == 100 +> NORMAL RUN: result for (ID: 46) ClassLoadObject { cnt: 1, curClass: 1} on Test1969 target thread - 46 +> Single call with force-early-return on (ID: 47) ClassLoadObject { cnt: 0, curClass: 1} +> Will force return of Test1969 target thread - 47 +> Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +> art.NonStandardExit.forceEarlyReturnVoid(Native Method) +> art.Test1969$TestSuspender.performForceReturn(Test1969.java) +> art.Test1969.runTestOn(Test1969.java) +> art.Test1969.runTestOn(Test1969.java) +> art.Test1969.runTestOn(Test1969.java) +> art.Test1969.runTests(Test1969.java) +> <Additional frames hidden> +> +> TC1.foo == 201 +> result for (ID: 47) ClassLoadObject { cnt: 1, curClass: 2} on Test1969 target thread - 47 +> Test stopped during class-load. +> NORMAL RUN: Single call with no interference on (ID: 48) ClassLoadObject { cnt: 0, curClass: 2} +> TC2.foo == 302 +> NORMAL RUN: result for (ID: 48) ClassLoadObject { cnt: 1, curClass: 3} on Test1969 target thread - 48 +> Single call with force-early-return on (ID: 49) ClassLoadObject { cnt: 0, curClass: 3} +> Will force return of Test1969 target thread - 49 +> Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME +> art.NonStandardExit.forceEarlyReturnVoid(Native Method) +> art.Test1969$TestSuspender.performForceReturn(Test1969.java) +> art.Test1969.runTestOn(Test1969.java) +> art.Test1969.runTestOn(Test1969.java) +> art.Test1969.runTestOn(Test1969.java) +> art.Test1969.runTests(Test1969.java) +> <Additional frames hidden> +> +> TC3.foo == 403 +> result for (ID: 49) ClassLoadObject { cnt: 1, curClass: 4} on Test1969 target thread - 49 diff --git a/test/1969-force-early-return-void/expected.txt b/test/1969-force-early-return-void/expected.txt new file mode 100644 index 0000000000..fc685b4fee --- /dev/null +++ b/test/1969-force-early-return-void/expected.txt @@ -0,0 +1,178 @@ +Test stopped using breakpoint +NORMAL RUN: Single call with no interference on (ID: 0) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 0) StandardTestObject { cnt: 2 } on Test1969 target thread - 0 +Single call with force-early-return on (ID: 1) StandardTestObject { cnt: 0 } +Will force return of Test1969 target thread - 1 +result for (ID: 1) StandardTestObject { cnt: 1 } on Test1969 target thread - 1 +Test stopped using breakpoint with declared synchronized function +NORMAL RUN: Single call with no interference on (ID: 2) SynchronizedFunctionTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 2) SynchronizedFunctionTestObject { cnt: 2 } on Test1969 target thread - 2 +Single call with force-early-return on (ID: 3) SynchronizedFunctionTestObject { cnt: 0 } +Will force return of Test1969 target thread - 3 +result for (ID: 3) SynchronizedFunctionTestObject { cnt: 1 } on Test1969 target thread - 3 +Test stopped using breakpoint with synchronized block +NORMAL RUN: Single call with no interference on (ID: 4) SynchronizedTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 4) SynchronizedTestObject { cnt: 2 } on Test1969 target thread - 4 +Single call with force-early-return on (ID: 5) SynchronizedTestObject { cnt: 0 } +Will force return of Test1969 target thread - 5 +result for (ID: 5) SynchronizedTestObject { cnt: 1 } on Test1969 target thread - 5 +Test stopped on single step +NORMAL RUN: Single call with no interference on (ID: 6) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 6) StandardTestObject { cnt: 2 } on Test1969 target thread - 6 +Single call with force-early-return on (ID: 7) StandardTestObject { cnt: 0 } +Will force return of Test1969 target thread - 7 +result for (ID: 7) StandardTestObject { cnt: 1 } on Test1969 target thread - 7 +Test stopped on field access +NORMAL RUN: Single call with no interference on (ID: 8) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 } +NORMAL RUN: result for (ID: 8) FieldBasedTestObject { TARGET_FIELD: 10, cnt: 2 } on Test1969 target thread - 8 +Single call with force-early-return on (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 } +Will force return of Test1969 target thread - 9 +result for (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 1 } on Test1969 target thread - 9 +Test stopped on field modification +NORMAL RUN: Single call with no interference on (ID: 10) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 } +NORMAL RUN: result for (ID: 10) FieldBasedTestObject { TARGET_FIELD: 10, cnt: 2 } on Test1969 target thread - 10 +Single call with force-early-return on (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 0 } +Will force return of Test1969 target thread - 11 +result for (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0, cnt: 1 } on Test1969 target thread - 11 +Test stopped during Method Exit of calledFunction +NORMAL RUN: Single call with no interference on (ID: 12) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 12) StandardTestObject { cnt: 2 } on Test1969 target thread - 12 +Single call with force-early-return on (ID: 13) StandardTestObject { cnt: 0 } +Will force return of Test1969 target thread - 13 +result for (ID: 13) StandardTestObject { cnt: 2 } on Test1969 target thread - 13 +Test stopped during Method Enter of calledFunction +NORMAL RUN: Single call with no interference on (ID: 14) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 14) StandardTestObject { cnt: 2 } on Test1969 target thread - 14 +Single call with force-early-return on (ID: 15) StandardTestObject { cnt: 0 } +Will force return of Test1969 target thread - 15 +result for (ID: 15) StandardTestObject { cnt: 0 } on Test1969 target thread - 15 +Test stopped during Method Exit due to exception thrown in same function +NORMAL RUN: Single call with no interference on (ID: 16) ExceptionOnceObject { cnt: 0, throwInSub: false } +Uncaught exception in thread Thread[Test1969 target thread - 16,5,main] - art.Test1969$ExceptionOnceObject$TestError: null + art.Test1969$ExceptionOnceObject.calledFunction(Test1969.java) + art.Test1969$AbstractTestObject.run(Test1969.java) + art.Test1969$2.run(Test1969.java) + java.lang.Thread.run(Thread.java) + +NORMAL RUN: result for (ID: 16) ExceptionOnceObject { cnt: 1, throwInSub: false } on Test1969 target thread - 16 +Single call with force-early-return on (ID: 17) ExceptionOnceObject { cnt: 0, throwInSub: false } +Will force return of Test1969 target thread - 17 +result for (ID: 17) ExceptionOnceObject { cnt: 1, throwInSub: false } on Test1969 target thread - 17 +Test stopped during Method Exit due to exception thrown in subroutine +NORMAL RUN: Single call with no interference on (ID: 18) ExceptionOnceObject { cnt: 0, throwInSub: true } +Uncaught exception in thread Thread[Test1969 target thread - 18,5,main] - art.Test1969$ExceptionOnceObject$TestError: null + art.Test1969$ExceptionOnceObject.doThrow(Test1969.java) + art.Test1969$ExceptionOnceObject.calledFunction(Test1969.java) + art.Test1969$AbstractTestObject.run(Test1969.java) + art.Test1969$2.run(Test1969.java) + java.lang.Thread.run(Thread.java) + +NORMAL RUN: result for (ID: 18) ExceptionOnceObject { cnt: 1, throwInSub: true } on Test1969 target thread - 18 +Single call with force-early-return on (ID: 19) ExceptionOnceObject { cnt: 0, throwInSub: true } +Will force return of Test1969 target thread - 19 +result for (ID: 19) ExceptionOnceObject { cnt: 1, throwInSub: true } on Test1969 target thread - 19 +Test stopped during notifyFramePop with exception on pop of calledFunction +NORMAL RUN: Single call with no interference on (ID: 20) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1969$ExceptionThrowTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 20) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 20 +Single call with force-early-return on (ID: 21) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of Test1969 target thread - 21 +result for (ID: 21) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 21 +Test stopped during notifyFramePop with exception on pop of doThrow +NORMAL RUN: Single call with no interference on (ID: 22) ExceptionCatchTestObject { cnt: 0 } +art.Test1969$ExceptionCatchTestObject$TestError caught in called function. +NORMAL RUN: result for (ID: 22) ExceptionCatchTestObject { cnt: 2 } on Test1969 target thread - 22 +Single call with force-early-return on (ID: 23) ExceptionCatchTestObject { cnt: 0 } +Will force return of Test1969 target thread - 23 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnVoid(Native Method) + art.Test1969$TestSuspender.performForceReturn(Test1969.java) + art.Test1969.runTestOn(Test1969.java) + art.Test1969.runTestOn(Test1969.java) + art.Test1969.runTestOn(Test1969.java) + art.Test1969.runTests(Test1969.java) + <Additional frames hidden> + +art.Test1969$ExceptionCatchTestObject$TestError caught in called function. +result for (ID: 23) ExceptionCatchTestObject { cnt: 2 } on Test1969 target thread - 23 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +NORMAL RUN: Single call with no interference on (ID: 24) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1969$ExceptionThrowTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 24) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } on Test1969 target thread - 24 +Single call with force-early-return on (ID: 25) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of Test1969 target thread - 25 +result for (ID: 25) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } on Test1969 target thread - 25 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +NORMAL RUN: Single call with no interference on (ID: 26) ExceptionCatchTestObject { cnt: 0 } +art.Test1969$ExceptionCatchTestObject$TestError caught in called function. +NORMAL RUN: result for (ID: 26) ExceptionCatchTestObject { cnt: 2 } on Test1969 target thread - 26 +Single call with force-early-return on (ID: 27) ExceptionCatchTestObject { cnt: 0 } +Will force return of Test1969 target thread - 27 +result for (ID: 27) ExceptionCatchTestObject { cnt: 1 } on Test1969 target thread - 27 +Test stopped during Exception event of calledFunction (catch in calling function) +NORMAL RUN: Single call with no interference on (ID: 28) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1969$ExceptionThrowTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 28) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 28 +Single call with force-early-return on (ID: 29) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of Test1969 target thread - 29 +result for (ID: 29) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 29 +Test stopped during Exception event of calledFunction (catch in called function) +NORMAL RUN: Single call with no interference on (ID: 30) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1969$ExceptionThrowTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 30) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } on Test1969 target thread - 30 +Single call with force-early-return on (ID: 31) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of Test1969 target thread - 31 +result for (ID: 31) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } on Test1969 target thread - 31 +Test stopped during Exception event of calledFunction (catch in parent of calling function) +NORMAL RUN: Single call with no interference on (ID: 32) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +art.Test1969$ExceptionThrowFarTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 32) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 32 +Single call with force-early-return on (ID: 33) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +Will force return of Test1969 target thread - 33 +result for (ID: 33) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } on Test1969 target thread - 33 +Test stopped during Exception event of calledFunction (catch in called function) +NORMAL RUN: Single call with no interference on (ID: 34) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +art.Test1969$ExceptionThrowFarTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 34) ExceptionThrowFarTestObject { cnt: 111, baseCnt: 2 } on Test1969 target thread - 34 +Single call with force-early-return on (ID: 35) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +Will force return of Test1969 target thread - 35 +result for (ID: 35) ExceptionThrowFarTestObject { cnt: 101, baseCnt: 2 } on Test1969 target thread - 35 +Test stopped during random Suspend. +NORMAL RUN: Single call with no interference on (ID: 36) SuspendSuddenlyObject { cnt: 0, spun: false } +NORMAL RUN: result for (ID: 36) SuspendSuddenlyObject { cnt: 2, spun: true } on Test1969 target thread - 36 +Single call with force-early-return on (ID: 37) SuspendSuddenlyObject { cnt: 0, spun: false } +Will force return of Test1969 target thread - 37 +result for (ID: 37) SuspendSuddenlyObject { cnt: 1, spun: true } on Test1969 target thread - 37 +Test stopped during a native method fails +NORMAL RUN: Single call with no interference on (ID: 38) NativeCalledObject { cnt: 0 } +NORMAL RUN: result for (ID: 38) NativeCalledObject { cnt: 2 } on Test1969 target thread - 38 +Single call with force-early-return on (ID: 39) NativeCalledObject { cnt: 0 } +Will force return of Test1969 target thread - 39 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.NonStandardExit.forceEarlyReturnVoid(Native Method) + art.Test1969$TestSuspender.performForceReturn(Test1969.java) + art.Test1969.runTestOn(Test1969.java) + art.Test1969.runTestOn(Test1969.java) + art.Test1969.runTestOn(Test1969.java) + art.Test1969.runTests(Test1969.java) + <Additional frames hidden> + +result for (ID: 39) NativeCalledObject { cnt: 2 } on Test1969 target thread - 39 +Test stopped in a method called by native succeeds +NORMAL RUN: Single call with no interference on (ID: 40) NativeCallerObject { cnt: 0 } +NORMAL RUN: result for (ID: 40) NativeCallerObject { cnt: 2 } on Test1969 target thread - 40 +Single call with force-early-return on (ID: 41) NativeCallerObject { cnt: 0 } +Will force return of Test1969 target thread - 41 +result for (ID: 41) NativeCallerObject { cnt: 2 } on Test1969 target thread - 41 +Test stopped in a static method +NORMAL RUN: Single call with no interference on (ID: 42) StaticMethodObject { cnt: 0 } +NORMAL RUN: result for (ID: 42) StaticMethodObject { cnt: 2 } on Test1969 target thread - 42 +Single call with force-early-return on (ID: 43) StaticMethodObject { cnt: 0 } +Will force return of Test1969 target thread - 43 +result for (ID: 43) StaticMethodObject { cnt: 1 } on Test1969 target thread - 43 +Test stopped in a Object <init> method +NORMAL RUN: Single call with no interference on (ID: 44) ObjectInitTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 44) ObjectInitTestObject { cnt: 2 } on Test1969 target thread - 44 +Single call with force-early-return on (ID: 45) ObjectInitTestObject { cnt: 0 } +Will force return of Test1969 target thread - 45 +result for (ID: 45) ObjectInitTestObject { cnt: 1 } on Test1969 target thread - 45 diff --git a/test/1969-force-early-return-void/force_early_return_void.cc b/test/1969-force-early-return-void/force_early_return_void.cc new file mode 100644 index 0000000000..29353623c7 --- /dev/null +++ b/test/1969-force-early-return-void/force_early_return_void.cc @@ -0,0 +1,102 @@ +/* + * 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" + +#include "suspend_event_helper.h" + +namespace art { +namespace Test1969ForceEarlyReturnVoid { + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1969_00024NativeCalledObject_calledFunction( + JNIEnv* env, jobject thiz) { + jclass klass = env->GetObjectClass(thiz); + jfieldID cnt = env->GetFieldID(klass, "cnt", "I"); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + void *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(/* thread */ nullptr, + reinterpret_cast<void**>(&data)))) { + return; + } + if (data != nullptr) { + art::common_suspend_event::PerformSuspension(jvmti_env, env); + } + return; +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1969_00024NativeCallerObject_run( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(1); + jclass klass = env->GetObjectClass(thiz); + jmethodID called = env->GetMethodID(klass, "calledFunction", "()V"); + env->CallVoidMethod(thiz, called); + env->PopLocalFrame(nullptr); +} + +extern "C" JNIEXPORT +jboolean JNICALL Java_art_Test1969_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 Test1969ForceEarlyReturnVoid +} // namespace art + diff --git a/test/1969-force-early-return-void/info.txt b/test/1969-force-early-return-void/info.txt new file mode 100644 index 0000000000..19fdb1a70a --- /dev/null +++ b/test/1969-force-early-return-void/info.txt @@ -0,0 +1,4 @@ +Test JVMTI ForceEarlyReturnVoid functionality + +Checks that we can call the ForceEarlyReturn functions successfully and force +returns of objects. It also checks some of the basic error modes. diff --git a/test/1969-force-early-return-void/run b/test/1969-force-early-return-void/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/1969-force-early-return-void/run @@ -0,0 +1,17 @@ +#!/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. + +./default-run "$@" --jvmti diff --git a/test/1969-force-early-return-void/src/Main.java b/test/1969-force-early-return-void/src/Main.java new file mode 100644 index 0000000000..e37c910d59 --- /dev/null +++ b/test/1969-force-early-return-void/src/Main.java @@ -0,0 +1,22 @@ +/* + * 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.Test1969.run(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")); + } +} diff --git a/test/1969-force-early-return-void/src/art/Breakpoint.java b/test/1969-force-early-return-void/src/art/Breakpoint.java new file mode 120000 index 0000000000..3673916cc6 --- /dev/null +++ b/test/1969-force-early-return-void/src/art/Breakpoint.java @@ -0,0 +1 @@ +../../../jvmti-common/Breakpoint.java
\ No newline at end of file diff --git a/test/1969-force-early-return-void/src/art/NonStandardExit.java b/test/1969-force-early-return-void/src/art/NonStandardExit.java new file mode 120000 index 0000000000..d542a3caa7 --- /dev/null +++ b/test/1969-force-early-return-void/src/art/NonStandardExit.java @@ -0,0 +1 @@ +../../../jvmti-common/NonStandardExit.java
\ No newline at end of file diff --git a/test/1969-force-early-return-void/src/art/StackTrace.java b/test/1969-force-early-return-void/src/art/StackTrace.java new file mode 120000 index 0000000000..e1a08aadbd --- /dev/null +++ b/test/1969-force-early-return-void/src/art/StackTrace.java @@ -0,0 +1 @@ +../../../jvmti-common/StackTrace.java
\ No newline at end of file diff --git a/test/1969-force-early-return-void/src/art/SuspendEvents.java b/test/1969-force-early-return-void/src/art/SuspendEvents.java new file mode 120000 index 0000000000..f7a5f7e327 --- /dev/null +++ b/test/1969-force-early-return-void/src/art/SuspendEvents.java @@ -0,0 +1 @@ +../../../jvmti-common/SuspendEvents.java
\ No newline at end of file diff --git a/test/1969-force-early-return-void/src/art/Suspension.java b/test/1969-force-early-return-void/src/art/Suspension.java new file mode 120000 index 0000000000..bcef96f69d --- /dev/null +++ b/test/1969-force-early-return-void/src/art/Suspension.java @@ -0,0 +1 @@ +../../../jvmti-common/Suspension.java
\ No newline at end of file diff --git a/test/1969-force-early-return-void/src/art/Test1969.java b/test/1969-force-early-return-void/src/art/Test1969.java new file mode 100644 index 0000000000..898da2743e --- /dev/null +++ b/test/1969-force-early-return-void/src/art/Test1969.java @@ -0,0 +1,973 @@ +/* + * 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 static art.SuspendEvents.EVENT_TYPE_CLASS_LOAD; +import static art.SuspendEvents.setupFieldSuspendFor; +import static art.SuspendEvents.setupSuspendBreakpointFor; +import static art.SuspendEvents.setupSuspendClassEvent; +import static art.SuspendEvents.setupSuspendExceptionEvent; +import static art.SuspendEvents.setupSuspendMethodEvent; +import static art.SuspendEvents.setupSuspendPopFrameEvent; +import static art.SuspendEvents.setupSuspendSingleStepAt; +import static art.SuspendEvents.setupTest; +import static art.SuspendEvents.waitForSuspendHit; + +import java.io.*; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class Test1969 { + public static final boolean PRINT_STACK_TRACE = false; + + public final boolean canRunClassLoadTests; + + public static void doNothing() {} + + public static interface TestSuspender { + public void setupForceReturnRun(Thread thr); + + public void waitForSuspend(Thread thr); + + public void cleanup(Thread thr); + + public default void performForceReturn(Thread thr) { + System.out.println("Will force return of " + thr.getName()); + NonStandardExit.forceEarlyReturnVoid(thr); + } + + public default void setupNormalRun(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 setupForceReturnRun(Thread thr) { + setup.run(thr); + } + + public void waitForSuspend(Thread thr) { + waitForSuspendHit(thr); + } + + public void cleanup(Thread thr) { + clean.run(thr); + } + }; + } + + public void runTestOn(Supplier<Runnable> testObj, ThreadRunnable su, ThreadRunnable cl) + throws Exception { + runTestOn(testObj, makeSuspend(su, cl)); + } + + private static void SafePrintStackTrace(StackTraceElement st[]) { + System.out.println(safeDumpStackTrace(st, "\t")); + } + + private static String safeDumpStackTrace(StackTraceElement st[], String prefix) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream os = new PrintStream(baos); + for (StackTraceElement e : st) { + os.println( + prefix + + e.getClassName() + + "." + + e.getMethodName() + + "(" + + (e.isNativeMethod() ? "Native Method" : e.getFileName()) + + ")"); + if (e.getClassName().equals("art.Test1969") && e.getMethodName().equals("runTests")) { + os.println(prefix + "<Additional frames hidden>"); + break; + } + } + os.flush(); + return baos.toString(); + } + + static long ID_COUNTER = 0; + + public Runnable Id(final Runnable tr) { + final long my_id = ID_COUNTER++; + return new Runnable() { + public void run() { + tr.run(); + } + + public String toString() { + return "(ID: " + my_id + ") " + tr.toString(); + } + }; + } + + public static long THREAD_COUNT = 0; + + public Thread mkThread(Runnable r) { + Thread t = new Thread(r, "Test1969 target thread - " + THREAD_COUNT++); + t.setUncaughtExceptionHandler( + (thr, e) -> { + System.out.println( + "Uncaught exception in thread " + + thr + + " - " + + e.getClass().getName() + + ": " + + e.getLocalizedMessage()); + SafePrintStackTrace(e.getStackTrace()); + }); + return t; + } + + final class TestConfig { + public final Runnable testObj; + public final TestSuspender suspender; + + public TestConfig(Runnable obj, TestSuspender su) { + this.testObj = obj; + this.suspender = su; + } + } + + public void runTestOn(Supplier<Runnable> testObjGen, TestSuspender su) throws Exception { + runTestOn(() -> new TestConfig(testObjGen.get(), su)); + } + + public void runTestOn(Supplier<TestConfig> config) throws Exception { + TestConfig normal_config = config.get(); + Runnable normal_run = Id(normal_config.testObj); + try { + System.out.println("NORMAL RUN: Single call with no interference on " + normal_run); + Thread normal_thread = mkThread(normal_run); + normal_config.suspender.setupNormalRun(normal_thread); + normal_thread.start(); + normal_thread.join(); + System.out.println("NORMAL RUN: result for " + normal_run + " on " + normal_thread.getName()); + } catch (Exception e) { + System.out.println("NORMAL RUN: Ended with exception for " + normal_run + "!"); + e.printStackTrace(System.out); + } + + TestConfig force_return_config = config.get(); + Runnable testObj = Id(force_return_config.testObj); + TestSuspender su = force_return_config.suspender; + System.out.println("Single call with force-early-return on " + testObj); + 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 = + mkThread( + () -> { + await.run(); + testObj.run(); + }); + thr.start(); + + // Wait until the other thread is started. + startup_latch.await(); + + // Setup suspension method on the thread. + su.setupForceReturnRun(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. + su.performForceReturn(thr); + } catch (Exception e) { + System.out.println("Failed to force-return 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 for " + testObj + " on " + thr.getName()); + } + + public abstract static class AbstractTestObject implements Runnable { + public AbstractTestObject() {} + + public void run() { + // This function should be force-early-returned. + calledFunction(); + } + + public abstract void calledFunction(); + } + + public static class FieldBasedTestObject extends AbstractTestObject implements Runnable { + public int TARGET_FIELD; + public int cnt = 0; + + public FieldBasedTestObject() { + super(); + TARGET_FIELD = 0; + } + + public void calledFunction() { + cnt++; + // We put a watchpoint here and force-early-return when we are at it. + TARGET_FIELD += 10; + cnt++; + } + + public String toString() { + return "FieldBasedTestObject { TARGET_FIELD: " + TARGET_FIELD + ", cnt: " + cnt + " }"; + } + } + + public static class StandardTestObject extends AbstractTestObject implements Runnable { + public int cnt; + + public StandardTestObject() { + super(); + cnt = 0; + } + + public void calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and force-early-return when we are at it. + doNothing(); // line +2 + cnt++; // line +3 + return; // line +4 + } + + 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 + cnt++; // line +3 + return; + } + + public String toString() { + return "SynchronizedFunctionTestObject { cnt: " + cnt + " }"; + } + } + + public static class SynchronizedTestObject extends AbstractTestObject implements Runnable { + public final Object lock; + public int cnt; + + public SynchronizedTestObject() { + super(); + lock = new Object(); + cnt = 0; + } + + 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 + cnt++; // line +4 + return; // line +5 + } + } + + 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(); + cnt += 100; + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in called function."); + cnt++; + } + return; + } + + public Object doThrow() { + throw new TestError(); + } + + public String toString() { + return "ExceptionCatchTestObject { cnt: " + cnt + " }"; + } + } + + public static class ExceptionThrowFarTestObject implements Runnable { + 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 void run() { + baseCallCnt++; + try { + callingFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + baseCallCnt++; + } + + public void callingFunction() { + calledFunction(); + } + + public void calledFunction() { + cnt++; + if (catchInCalled) { + try { + cnt += 100; + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + doNothing(); + cnt += 10; + return; + } + } else { + cnt++; + throw new TestError(); // We put a watch here. + } + } + + public String toString() { + return "ExceptionThrowFarTestObject { cnt: " + cnt + ", baseCnt: " + baseCallCnt + " }"; + } + } + + 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(); + } + } + return; + } + + public void doThrow() { + throw new TestError(); + } + + public String toString() { + return "ExceptionOnceObject { cnt: " + cnt + ", throwInSub: " + throwInSub + " }"; + } + } + + public static class ExceptionThrowTestObject implements Runnable { + 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 void run() { + baseCallCnt++; + try { + calledFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + baseCallCnt++; + } + + public void calledFunction() { + cnt++; + if (catchInCalled) { + try { + cnt += 10; + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + doNothing(); + cnt += 100; + return; + } + } else { + cnt += 1; + throw new TestError(); // We put a watch here. + } + } + + public String toString() { + return "ExceptionThrowTestObject { cnt: " + cnt + ", baseCnt: " + baseCallCnt + " }"; + } + } + + 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 Runnable { + public Object returnValue = null; + public int cnt = 0; + + public Object getReturnValue() { + return returnValue; + } + + public native void run(); + + public void calledFunction() { + cnt++; + // We will stop using a MethodExit event. + doNothing(); + cnt++; + return; + } + + public String toString() { + return "NativeCallerObject { cnt: " + cnt + " }"; + } + } + + public static class ClassLoadObject implements Runnable { + public int cnt; + + public static final String[] CLASS_NAMES = + new String[] { + "Lart/Test1969$ClassLoadObject$TC0;", + "Lart/Test1969$ClassLoadObject$TC1;", + "Lart/Test1969$ClassLoadObject$TC2;", + "Lart/Test1969$ClassLoadObject$TC3;", + "Lart/Test1969$ClassLoadObject$TC4;", + "Lart/Test1969$ClassLoadObject$TC5;", + "Lart/Test1969$ClassLoadObject$TC6;", + "Lart/Test1969$ClassLoadObject$TC7;", + "Lart/Test1969$ClassLoadObject$TC8;", + "Lart/Test1969$ClassLoadObject$TC9;", + }; + + private static int curClass = 0; + + private static class TC0 { public static int foo; static { foo = 100 + curClass; } } + + private static class TC1 { public static int foo; static { foo = 200 + curClass; } } + + private static class TC2 { public static int foo; static { foo = 300 + curClass; } } + + private static class TC3 { public static int foo; static { foo = 400 + curClass; } } + + private static class TC4 { public static int foo; static { foo = 500 + curClass; } } + + private static class TC5 { public static int foo; static { foo = 600 + curClass; } } + + private static class TC6 { public static int foo; static { foo = 700 + curClass; } } + + private static class TC7 { public static int foo; static { foo = 800 + curClass; } } + + private static class TC8 { public static int foo; static { foo = 900 + curClass; } } + + private static class TC9 { public static int foo; static { foo = 1000 + curClass; } } + + public ClassLoadObject() { + super(); + cnt = 0; + } + + public void run() { + if (curClass == 0) { + calledFunction0(); + } else if (curClass == 1) { + calledFunction1(); + } else if (curClass == 2) { + calledFunction2(); + } else if (curClass == 3) { + calledFunction3(); + } else if (curClass == 4) { + calledFunction4(); + } else if (curClass == 5) { + calledFunction5(); + } else if (curClass == 6) { + calledFunction6(); + } else if (curClass == 7) { + calledFunction7(); + } else if (curClass == 8) { + calledFunction8(); + } else if (curClass == 9) { + calledFunction9(); + } + curClass++; + } + + public void calledFunction0() { + cnt++; + System.out.println("TC0.foo == " + TC0.foo); + } + + public void calledFunction1() { + cnt++; + System.out.println("TC1.foo == " + TC1.foo); + } + + public void calledFunction2() { + cnt++; + System.out.println("TC2.foo == " + TC2.foo); + } + + public void calledFunction3() { + cnt++; + System.out.println("TC3.foo == " + TC3.foo); + } + + public void calledFunction4() { + cnt++; + System.out.println("TC4.foo == " + TC4.foo); + } + + public void calledFunction5() { + cnt++; + System.out.println("TC5.foo == " + TC5.foo); + } + + public void calledFunction6() { + cnt++; + System.out.println("TC6.foo == " + TC6.foo); + } + + public void calledFunction7() { + cnt++; + System.out.println("TC7.foo == " + TC7.foo); + } + + public void calledFunction8() { + cnt++; + System.out.println("TC8.foo == " + TC8.foo); + } + + public void calledFunction9() { + cnt++; + System.out.println("TC9.foo == " + TC9.foo); + } + + public String toString() { + return "ClassLoadObject { cnt: " + cnt + ", curClass: " + curClass + "}"; + } + } + + public static class ObjectInitTestObject implements Runnable { + // TODO How do we do this for <clinit> + public int cnt = 0; + public static final class ObjectInitTarget { + public ObjectInitTarget(Runnable r) { + super(); // line +0 + r.run(); // line +1 + // We set a breakpoint here and force-early-return + doNothing(); // line +3 + r.run(); // line +4 + } + } + + public void run() { + new ObjectInitTarget(() -> cnt++); + } + + public String toString() { + return "ObjectInitTestObject { cnt: " + cnt + " }"; + } + } + + public static class SuspendSuddenlyObject extends AbstractTestObject { + public volatile boolean should_spin = true; + public volatile boolean is_spinning = false; + public int cnt = 0; + + public void calledFunction() { + cnt++; + do { + is_spinning = true; + } while (should_spin); + cnt++; + return; + } + + public String toString() { + return "SuspendSuddenlyObject { cnt: " + cnt + ", spun: " + is_spinning + " }"; + } + } + + public static final class StaticMethodObject implements Runnable { + public int cnt = 0; + + public static void calledFunction(Runnable incr) { + incr.run(); // line +0 + // We put a breakpoint here to force the return. + doNothing(); // line +2 + incr.run(); // line +3 + return; // line +4 + } + + public void run() { + calledFunction(() -> cnt++); + } + + public final String toString() { + return "StaticMethodObject { cnt: " + cnt + " }"; + } + } + + // Only used by CTS to run without class-load tests. + public static void run() throws Exception { + new Test1969(false).runTests(); + } + public static void run(boolean canRunClassLoadTests) throws Exception { + new Test1969(canRunClassLoadTests).runTests(); + } + + public Test1969(boolean canRunClassLoadTests) { + this.canRunClassLoadTests = canRunClassLoadTests; + } + + public static void no_runTestOn(Supplier<Object> a, ThreadRunnable b, ThreadRunnable c) {} + + public void runTests() throws Exception { + setupTest(); + + final Method calledFunction = StandardTestObject.class.getDeclaredMethod("calledFunction"); + // 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( + StandardTestObject::new, + (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr), + SuspendEvents::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( + SynchronizedFunctionTestObject::new, + (thr) -> setupSuspendBreakpointFor(syncFunctionCalledFunction, syncFunctionLoc, thr), + SuspendEvents::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( + SynchronizedTestObject::new, + (thr) -> setupSuspendBreakpointFor(syncCalledFunction, syncLoc, thr), + SuspendEvents::clearSuspendBreakpointFor); + + System.out.println("Test stopped on single step"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendSingleStepAt(calledFunction, loc, thr), + SuspendEvents::clearSuspendSingleStepFor); + + final Field target_field = FieldBasedTestObject.class.getDeclaredField("TARGET_FIELD"); + System.out.println("Test stopped on field access"); + runTestOn( + FieldBasedTestObject::new, + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, true, thr), + SuspendEvents::clearFieldSuspendFor); + + System.out.println("Test stopped on field modification"); + runTestOn( + FieldBasedTestObject::new, + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, false, thr), + SuspendEvents::clearFieldSuspendFor); + + System.out.println("Test stopped during Method Exit of calledFunction"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendMethodEvent(calledFunction, /* enter */ false, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test stopped during Method Enter of calledFunction"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendMethodEvent(calledFunction, /* enter */ true, thr), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::clearSuspendMethodEvent); + + 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), + SuspendEvents::clearSuspendPopFrameEvent); + + final Method exceptionCatchThrowMethod = + ExceptionCatchTestObject.class.getDeclaredMethod("doThrow"); + System.out.println("Test stopped during notifyFramePop with exception on pop of doThrow"); + runTestOn( + ExceptionCatchTestObject::new, + (thr) -> setupSuspendPopFrameEvent(0, exceptionCatchThrowMethod, thr), + SuspendEvents::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), + SuspendEvents::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( + ExceptionCatchTestObject::new, + (thr) -> setupSuspendExceptionEvent(exceptionCatchCalledMethod, /* catch */ true, thr), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::clearSuspendExceptionEvent); + + System.out.println("Test stopped during random Suspend."); + runTestOn( + () -> { + final SuspendSuddenlyObject sso = new SuspendSuddenlyObject(); + return new TestConfig( + sso, + new TestSuspender() { + public void setupForceReturnRun(Thread thr) {} + + public void setupNormalRun(Thread thr) { + sso.should_spin = false; + } + + public void waitForSuspend(Thread thr) { + while (!sso.is_spinning) {} + Suspension.suspend(thr); + } + + public void cleanup(Thread thr) {} + }); + }); + + System.out.println("Test stopped during a native method fails"); + runTestOn( + NativeCalledObject::new, + SuspendEvents::setupWaitForNativeCall, + SuspendEvents::clearWaitForNativeCall); + + System.out.println("Test stopped in a method called by native succeeds"); + final Method nativeCallerMethod = NativeCallerObject.class.getDeclaredMethod("calledFunction"); + runTestOn( + NativeCallerObject::new, + (thr) -> setupSuspendMethodEvent(nativeCallerMethod, /* enter */ false, thr), + SuspendEvents::clearSuspendMethodEvent); + + + System.out.println("Test stopped in a static method"); + final Method staticCalledMethod = StaticMethodObject.class.getDeclaredMethod("calledFunction", Runnable.class); + final int staticFunctionLine= Breakpoint.locationToLine(staticCalledMethod, 0) + 2; + final long staticFunctionLoc = Breakpoint.lineToLocation(staticCalledMethod, staticFunctionLine); + runTestOn( + StaticMethodObject::new, + (thr) -> setupSuspendBreakpointFor(staticCalledMethod, staticFunctionLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test stopped in a Object <init> method"); + final Executable initCalledMethod = ObjectInitTestObject.ObjectInitTarget.class.getConstructor(Runnable.class); + final int initFunctionLine= Breakpoint.locationToLine(initCalledMethod, 0) + 3; + final long initFunctionLoc = Breakpoint.lineToLocation(initCalledMethod, initFunctionLine); + runTestOn( + ObjectInitTestObject::new, + (thr) -> setupSuspendBreakpointFor(initCalledMethod, initFunctionLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + if (canRunClassLoadTests && CanRunClassLoadingTests()) { + System.out.println("Test stopped during class-load."); + runTestOn( + ClassLoadObject::new, + (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_LOAD, ClassLoadObject.CLASS_NAMES, thr), + SuspendEvents::clearSuspendClassEvent); + System.out.println("Test stopped during class-load."); + runTestOn( + ClassLoadObject::new, + (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_LOAD, ClassLoadObject.CLASS_NAMES, thr), + SuspendEvents::clearSuspendClassEvent); + } + } + + + // 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/Test1969$RCLT1;"); + ranClassLoadTest = true; + return classesPreverified; + } + + public static native boolean isClassLoaded(String name); +} diff --git a/test/1970-force-early-return-long/expected.txt b/test/1970-force-early-return-long/expected.txt new file mode 100644 index 0000000000..ab79587646 --- /dev/null +++ b/test/1970-force-early-return-long/expected.txt @@ -0,0 +1,222 @@ +Test stopped using breakpoint +NORMAL RUN: Single call with no interference on (ID: 0) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 0) StandardTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 1) StandardTestObject { cnt: 0 } +Will force return of 987000 +result for (ID: 1) StandardTestObject { cnt: 1 } is 987000 +Test stopped using breakpoint with declared synchronized function +NORMAL RUN: Single call with no interference on (ID: 2) SynchronizedFunctionTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 2) SynchronizedFunctionTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 3) SynchronizedFunctionTestObject { cnt: 0 } +Will force return of 987001 +result for (ID: 3) SynchronizedFunctionTestObject { cnt: 1 } is 987001 +Test stopped using breakpoint with synchronized block +NORMAL RUN: Single call with no interference on (ID: 4) SynchronizedTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 4) SynchronizedTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 5) SynchronizedTestObject { cnt: 0 } +Will force return of 987002 +result for (ID: 5) SynchronizedTestObject { cnt: 1 } is 987002 +Test stopped on single step +NORMAL RUN: Single call with no interference on (ID: 6) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 6) StandardTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 7) StandardTestObject { cnt: 0 } +Will force return of 987003 +result for (ID: 7) StandardTestObject { cnt: 1 } is 987003 +Test stopped on field access +NORMAL RUN: Single call with no interference on (ID: 8) FieldBasedTestObject { TARGET_FIELD: 0 } +NORMAL RUN: result for (ID: 8) FieldBasedTestObject { TARGET_FIELD: 10 } is 10 +Single call with force-early-return on (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0 } +Will force return of 987004 +result for (ID: 9) FieldBasedTestObject { TARGET_FIELD: 0 } is 987004 +Test stopped on field modification +NORMAL RUN: Single call with no interference on (ID: 10) FieldBasedTestObject { TARGET_FIELD: 0 } +NORMAL RUN: result for (ID: 10) FieldBasedTestObject { TARGET_FIELD: 10 } is 10 +Single call with force-early-return on (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0 } +Will force return of 987005 +result for (ID: 11) FieldBasedTestObject { TARGET_FIELD: 0 } is 987005 +Test stopped during Method Exit of calledFunction +NORMAL RUN: Single call with no interference on (ID: 12) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 12) StandardTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 13) StandardTestObject { cnt: 0 } +Will force return of 987006 +result for (ID: 13) StandardTestObject { cnt: 2 } is 987006 +Test stopped during Method Enter of calledFunction +NORMAL RUN: Single call with no interference on (ID: 14) StandardTestObject { cnt: 0 } +NORMAL RUN: result for (ID: 14) StandardTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 15) StandardTestObject { cnt: 0 } +Will force return of 987007 +result for (ID: 15) StandardTestObject { cnt: 0 } is 987007 +Test stopped during Method Exit due to exception thrown in same function +NORMAL RUN: Single call with no interference on (ID: 16) ExceptionOnceObject { cnt: 0, throwInSub: false } +Uncaught exception in thread Thread[Test1970 target thread - 16,5,main] - art.Test1970$ExceptionOnceObject$TestError: null + art.Test1970$ExceptionOnceObject.calledFunction(Test1970.java) + art.Test1970$AbstractTestObject.run(Test1970.java) + art.Test1970$2.run(Test1970.java) + java.lang.Thread.run(Thread.java) + +NORMAL RUN: result for (ID: 16) ExceptionOnceObject { cnt: 1, throwInSub: false } is 0 +Single call with force-early-return on (ID: 17) ExceptionOnceObject { cnt: 0, throwInSub: false } +Will force return of 987008 +result for (ID: 17) ExceptionOnceObject { cnt: 1, throwInSub: false } is 987008 +Test stopped during Method Exit due to exception thrown in subroutine +NORMAL RUN: Single call with no interference on (ID: 18) ExceptionOnceObject { cnt: 0, throwInSub: true } +Uncaught exception in thread Thread[Test1970 target thread - 18,5,main] - art.Test1970$ExceptionOnceObject$TestError: null + art.Test1970$ExceptionOnceObject.doThrow(Test1970.java) + art.Test1970$ExceptionOnceObject.calledFunction(Test1970.java) + art.Test1970$AbstractTestObject.run(Test1970.java) + art.Test1970$2.run(Test1970.java) + java.lang.Thread.run(Thread.java) + +NORMAL RUN: result for (ID: 18) ExceptionOnceObject { cnt: 1, throwInSub: true } is 0 +Single call with force-early-return on (ID: 19) ExceptionOnceObject { cnt: 0, throwInSub: true } +Will force return of 987009 +result for (ID: 19) ExceptionOnceObject { cnt: 1, throwInSub: true } is 987009 +Test stopped during notifyFramePop with exception on pop of calledFunction +NORMAL RUN: Single call with no interference on (ID: 20) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1970$ExceptionThrowTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 20) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is 0 +Single call with force-early-return on (ID: 21) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of 987010 +result for (ID: 21) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is 987010 +Test stopped during notifyFramePop with exception on pop of doThrow +NORMAL RUN: Single call with no interference on (ID: 22) ExceptionCatchTestObject { cnt: 0 } +art.Test1970$ExceptionCatchTestObject$TestError caught in called function. +NORMAL RUN: result for (ID: 22) ExceptionCatchTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 23) ExceptionCatchTestObject { cnt: 0 } +Will force return of 987011 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnLong(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1970$TestSuspender.performForceReturn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTests(Test1970.java) + <Additional frames hidden> + +art.Test1970$ExceptionCatchTestObject$TestError caught in called function. +result for (ID: 23) ExceptionCatchTestObject { cnt: 2 } is 1 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function) +NORMAL RUN: Single call with no interference on (ID: 24) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1970$ExceptionThrowTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 24) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } is 11 +Single call with force-early-return on (ID: 25) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of 987012 +result for (ID: 25) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } is 987012 +Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine) +NORMAL RUN: Single call with no interference on (ID: 26) ExceptionCatchTestObject { cnt: 0 } +art.Test1970$ExceptionCatchTestObject$TestError caught in called function. +NORMAL RUN: result for (ID: 26) ExceptionCatchTestObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 27) ExceptionCatchTestObject { cnt: 0 } +Will force return of 987013 +result for (ID: 27) ExceptionCatchTestObject { cnt: 1 } is 987013 +Test stopped during Exception event of calledFunction (catch in calling function) +NORMAL RUN: Single call with no interference on (ID: 28) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1970$ExceptionThrowTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 28) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is 0 +Single call with force-early-return on (ID: 29) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of 987014 +result for (ID: 29) ExceptionThrowTestObject { cnt: 2, baseCnt: 2 } is 987014 +Test stopped during Exception event of calledFunction (catch in called function) +NORMAL RUN: Single call with no interference on (ID: 30) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +art.Test1970$ExceptionThrowTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 30) ExceptionThrowTestObject { cnt: 111, baseCnt: 2 } is 11 +Single call with force-early-return on (ID: 31) ExceptionThrowTestObject { cnt: 0, baseCnt: 0 } +Will force return of 987015 +result for (ID: 31) ExceptionThrowTestObject { cnt: 11, baseCnt: 2 } is 987015 +Test stopped during Exception event of calledFunction (catch in parent of calling function) +NORMAL RUN: Single call with no interference on (ID: 32) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +art.Test1970$ExceptionThrowFarTestObject$TestError thrown and caught! +NORMAL RUN: result for (ID: 32) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } is 0 +Single call with force-early-return on (ID: 33) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +Will force return of 987016 +result for (ID: 33) ExceptionThrowFarTestObject { cnt: 2, baseCnt: 2 } is 987016 +Test stopped during Exception event of calledFunction (catch in called function) +NORMAL RUN: Single call with no interference on (ID: 34) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +art.Test1970$ExceptionThrowFarTestObject$TestError caught in same function. +NORMAL RUN: result for (ID: 34) ExceptionThrowFarTestObject { cnt: 111, baseCnt: 2 } is 101 +Single call with force-early-return on (ID: 35) ExceptionThrowFarTestObject { cnt: 0, baseCnt: 0 } +Will force return of 987017 +result for (ID: 35) ExceptionThrowFarTestObject { cnt: 101, baseCnt: 2 } is 987017 +Test stopped during random Suspend. +NORMAL RUN: Single call with no interference on (ID: 36) SuspendSuddenlyObject { cnt: 0, spun: false } +NORMAL RUN: result for (ID: 36) SuspendSuddenlyObject { cnt: 2, spun: true } is 1 +Single call with force-early-return on (ID: 37) SuspendSuddenlyObject { cnt: 0, spun: false } +Will force return of 987018 +result for (ID: 37) SuspendSuddenlyObject { cnt: 1, spun: true } is 987018 +Test stopped during a native method fails +NORMAL RUN: Single call with no interference on (ID: 38) NativeCalledObject { cnt: 0 } +NORMAL RUN: result for (ID: 38) NativeCalledObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 39) NativeCalledObject { cnt: 0 } +Will force return of 987019 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME + art.NonStandardExit.forceEarlyReturnLong(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1970$TestSuspender.performForceReturn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTests(Test1970.java) + <Additional frames hidden> + +result for (ID: 39) NativeCalledObject { cnt: 2 } is 1 +Test stopped in a method called by native succeeds +NORMAL RUN: Single call with no interference on (ID: 40) NativeCallerObject { cnt: 0 } +NORMAL RUN: result for (ID: 40) NativeCallerObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 41) NativeCallerObject { cnt: 0 } +Will force return of 987020 +result for (ID: 41) NativeCallerObject { cnt: 2 } is 987020 +Test stopped in a static method +NORMAL RUN: Single call with no interference on (ID: 42) StaticMethodObject { cnt: 0 } +NORMAL RUN: result for (ID: 42) StaticMethodObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 43) StaticMethodObject { cnt: 0 } +Will force return of 987021 +result for (ID: 43) StaticMethodObject { cnt: 1 } is 987021 +Test force-return of void function fails! +NORMAL RUN: Single call with no interference on (ID: 44) BadForceVoidObject { cnt: 0 } +NORMAL RUN: result for (ID: 44) BadForceVoidObject { cnt: 2 } is -1 +Single call with force-early-return on (ID: 45) BadForceVoidObject { cnt: 0 } +Will force return of 987022 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnLong(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1970$TestSuspender.performForceReturn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTests(Test1970.java) + <Additional frames hidden> + +result for (ID: 45) BadForceVoidObject { cnt: 2 } is -1 +Test force-return of int function fails! +NORMAL RUN: Single call with no interference on (ID: 46) BadForceIntObject { cnt: 0 } +NORMAL RUN: result for (ID: 46) BadForceIntObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 47) BadForceIntObject { cnt: 0 } +Will force return of 987023 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnLong(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1970$TestSuspender.performForceReturn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTests(Test1970.java) + <Additional frames hidden> + +result for (ID: 47) BadForceIntObject { cnt: 2 } is 1 +Test force-return of Object function fails! +NORMAL RUN: Single call with no interference on (ID: 48) BadForceIntObject { cnt: 0 } +NORMAL RUN: result for (ID: 48) BadForceIntObject { cnt: 2 } is 1 +Single call with force-early-return on (ID: 49) BadForceIntObject { cnt: 0 } +Will force return of 987024 +Failed to force-return due to java.lang.RuntimeException: JVMTI_ERROR_TYPE_MISMATCH + art.NonStandardExit.forceEarlyReturnLong(Native Method) + art.NonStandardExit.forceEarlyReturn(NonStandardExit.java) + art.Test1970$TestSuspender.performForceReturn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTestOn(Test1970.java) + art.Test1970.runTests(Test1970.java) + <Additional frames hidden> + +result for (ID: 49) BadForceIntObject { cnt: 2 } is 1 diff --git a/test/1970-force-early-return-long/force_early_return_long.cc b/test/1970-force-early-return-long/force_early_return_long.cc new file mode 100644 index 0000000000..da0c946e29 --- /dev/null +++ b/test/1970-force-early-return-long/force_early_return_long.cc @@ -0,0 +1,81 @@ +/* + * 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" + +#include "suspend_event_helper.h" + +namespace art { +namespace Test1970ForceEarlyReturnLong { + +extern "C" JNIEXPORT +jlong JNICALL Java_art_Test1970_00024NativeCalledObject_calledFunction( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(4); + jclass klass = env->GetObjectClass(thiz); + jfieldID cnt = env->GetFieldID(klass, "cnt", "I"); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + jlong res = static_cast<jlong>(env->GetIntField(thiz, cnt)); + env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1); + void *data; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->GetThreadLocalStorage(/* thread */ nullptr, + reinterpret_cast<void**>(&data)))) { + env->PopLocalFrame(nullptr); + return -1; + } + if (data != nullptr) { + art::common_suspend_event::PerformSuspension(jvmti_env, env); + } + env->PopLocalFrame(nullptr); + return res; +} + +extern "C" JNIEXPORT +void JNICALL Java_art_Test1970_00024NativeCallerObject_run( + JNIEnv* env, jobject thiz) { + env->PushLocalFrame(1); + jclass klass = env->GetObjectClass(thiz); + jfieldID ret = env->GetFieldID(klass, "returnValue", "J"); + jmethodID called = env->GetMethodID(klass, "calledFunction", "()J"); + env->SetLongField(thiz, ret, env->CallLongMethod(thiz, called)); + env->PopLocalFrame(nullptr); +} + +} // namespace Test1970ForceEarlyReturnLong +} // namespace art + diff --git a/test/1970-force-early-return-long/info.txt b/test/1970-force-early-return-long/info.txt new file mode 100644 index 0000000000..621d881dce --- /dev/null +++ b/test/1970-force-early-return-long/info.txt @@ -0,0 +1,4 @@ +Test JVMTI ForceEarlyReturnObject functionality + +Checks that we can call the ForceEarlyReturn functions successfully and force +returns of objects. It also checks some of the basic error modes. diff --git a/test/1970-force-early-return-long/run b/test/1970-force-early-return-long/run new file mode 100755 index 0000000000..d16d4e6091 --- /dev/null +++ b/test/1970-force-early-return-long/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/1970-force-early-return-long/src/Main.java b/test/1970-force-early-return-long/src/Main.java new file mode 100644 index 0000000000..5a75458bf5 --- /dev/null +++ b/test/1970-force-early-return-long/src/Main.java @@ -0,0 +1,22 @@ +/* + * 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.Test1970.run(); + } +} diff --git a/test/1970-force-early-return-long/src/art/Breakpoint.java b/test/1970-force-early-return-long/src/art/Breakpoint.java new file mode 120000 index 0000000000..3673916cc6 --- /dev/null +++ b/test/1970-force-early-return-long/src/art/Breakpoint.java @@ -0,0 +1 @@ +../../../jvmti-common/Breakpoint.java
\ No newline at end of file diff --git a/test/1970-force-early-return-long/src/art/NonStandardExit.java b/test/1970-force-early-return-long/src/art/NonStandardExit.java new file mode 120000 index 0000000000..d542a3caa7 --- /dev/null +++ b/test/1970-force-early-return-long/src/art/NonStandardExit.java @@ -0,0 +1 @@ +../../../jvmti-common/NonStandardExit.java
\ No newline at end of file diff --git a/test/1970-force-early-return-long/src/art/StackTrace.java b/test/1970-force-early-return-long/src/art/StackTrace.java new file mode 120000 index 0000000000..e1a08aadbd --- /dev/null +++ b/test/1970-force-early-return-long/src/art/StackTrace.java @@ -0,0 +1 @@ +../../../jvmti-common/StackTrace.java
\ No newline at end of file diff --git a/test/1970-force-early-return-long/src/art/SuspendEvents.java b/test/1970-force-early-return-long/src/art/SuspendEvents.java new file mode 120000 index 0000000000..f7a5f7e327 --- /dev/null +++ b/test/1970-force-early-return-long/src/art/SuspendEvents.java @@ -0,0 +1 @@ +../../../jvmti-common/SuspendEvents.java
\ No newline at end of file diff --git a/test/1970-force-early-return-long/src/art/Suspension.java b/test/1970-force-early-return-long/src/art/Suspension.java new file mode 120000 index 0000000000..bcef96f69d --- /dev/null +++ b/test/1970-force-early-return-long/src/art/Suspension.java @@ -0,0 +1 @@ +../../../jvmti-common/Suspension.java
\ No newline at end of file diff --git a/test/1970-force-early-return-long/src/art/Test1970.java b/test/1970-force-early-return-long/src/art/Test1970.java new file mode 100644 index 0000000000..976d4e96e0 --- /dev/null +++ b/test/1970-force-early-return-long/src/art/Test1970.java @@ -0,0 +1,887 @@ +/* + * 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 static art.SuspendEvents.setupFieldSuspendFor; +import static art.SuspendEvents.setupSuspendBreakpointFor; +import static art.SuspendEvents.setupSuspendExceptionEvent; +import static art.SuspendEvents.setupSuspendMethodEvent; +import static art.SuspendEvents.setupSuspendPopFrameEvent; +import static art.SuspendEvents.setupSuspendSingleStepAt; +import static art.SuspendEvents.setupTest; +import static art.SuspendEvents.waitForSuspendHit; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class Test1970 { + // Make sure this is always high enough that it's easily distinguishable from the results the + // methods would normally return. + public static long OVERRIDE_ID = 987000; + + // Returns a value to be used for the return value of the given thread. + public static long getOveriddenReturnValue(Thread thr) { + return OVERRIDE_ID++; + } + + public static void doNothing() {} + + public interface TestRunnable extends Runnable { + public long getReturnValue(); + } + + public static interface TestSuspender { + public void setupForceReturnRun(Thread thr); + + public void waitForSuspend(Thread thr); + + public void cleanup(Thread thr); + + public default void performForceReturn(Thread thr) { + long ret = getOveriddenReturnValue(thr); + System.out.println("Will force return of " + ret); + NonStandardExit.forceEarlyReturn(thr, ret); + } + + public default void setupNormalRun(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 setupForceReturnRun(Thread thr) { + setup.run(thr); + } + + public void waitForSuspend(Thread thr) { + waitForSuspendHit(thr); + } + + public void cleanup(Thread thr) { + clean.run(thr); + } + }; + } + + public void runTestOn(Supplier<TestRunnable> testObj, ThreadRunnable su, ThreadRunnable cl) + throws Exception { + runTestOn(testObj, makeSuspend(su, cl)); + } + + private static void SafePrintStackTrace(StackTraceElement st[]) { + System.out.println(safeDumpStackTrace(st, "\t")); + } + + private static String safeDumpStackTrace(StackTraceElement st[], String prefix) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream os = new PrintStream(baos); + for (StackTraceElement e : st) { + os.println( + prefix + + e.getClassName() + + "." + + e.getMethodName() + + "(" + + (e.isNativeMethod() ? "Native Method" : e.getFileName()) + + ")"); + if (e.getClassName().equals("art.Test1970") && e.getMethodName().equals("runTests")) { + os.println(prefix + "<Additional frames hidden>"); + break; + } + } + os.flush(); + return baos.toString(); + } + + static long ID_COUNTER = 0; + + public TestRunnable Id(final TestRunnable tr) { + final long my_id = ID_COUNTER++; + return new TestRunnable() { + public void run() { + tr.run(); + } + + public long getReturnValue() { + return tr.getReturnValue(); + } + + public String toString() { + return "(ID: " + my_id + ") " + tr.toString(); + } + }; + } + + public static long THREAD_COUNT = 0; + + public Thread mkThread(Runnable r) { + Thread t = new Thread(r, "Test1970 target thread - " + THREAD_COUNT++); + t.setUncaughtExceptionHandler( + (thr, e) -> { + System.out.println( + "Uncaught exception in thread " + + thr + + " - " + + e.getClass().getName() + + ": " + + e.getLocalizedMessage()); + SafePrintStackTrace(e.getStackTrace()); + }); + return t; + } + + final class TestConfig { + public final TestRunnable testObj; + public final TestSuspender suspender; + + public TestConfig(TestRunnable obj, TestSuspender su) { + this.testObj = obj; + this.suspender = su; + } + } + + public void runTestOn(Supplier<TestRunnable> testObjGen, TestSuspender su) throws Exception { + runTestOn(() -> new TestConfig(testObjGen.get(), su)); + } + + public void runTestOn(Supplier<TestConfig> config) throws Exception { + TestConfig normal_config = config.get(); + TestRunnable normal_run = Id(normal_config.testObj); + try { + System.out.println("NORMAL RUN: Single call with no interference on " + normal_run); + Thread normal_thread = mkThread(normal_run); + normal_config.suspender.setupNormalRun(normal_thread); + normal_thread.start(); + normal_thread.join(); + System.out.println( + "NORMAL RUN: result for " + normal_run + " is " + normal_run.getReturnValue()); + } catch (Exception e) { + System.out.println("NORMAL RUN: Ended with exception for " + normal_run + "!"); + e.printStackTrace(System.out); + } + + TestConfig force_return_config = config.get(); + TestRunnable testObj = Id(force_return_config.testObj); + TestSuspender su = force_return_config.suspender; + System.out.println("Single call with force-early-return on " + testObj); + 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 = + mkThread( + () -> { + await.run(); + testObj.run(); + }); + thr.start(); + + // Wait until the other thread is started. + startup_latch.await(); + + // Setup suspension method on the thread. + su.setupForceReturnRun(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. + su.performForceReturn(thr); + } catch (Exception e) { + System.out.println("Failed to force-return 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 for " + testObj + " is " + testObj.getReturnValue()); + } + + public abstract static class AbstractTestObject implements TestRunnable { + private long resultVal = 0; + + public AbstractTestObject() { } + + public long getReturnValue() { + return resultVal; + } + + public void run() { + // This function should have it's return-value replaced by force-early-return. + resultVal = calledFunction(); + } + + public abstract long calledFunction(); + } + + public static class IntContainer { + private final int value; + + public IntContainer(int i) { + value = i; + } + + public String toString() { + return "IntContainer { value: " + value + " }"; + } + } + + public static class FieldBasedTestObject extends AbstractTestObject implements Runnable { + public int TARGET_FIELD; + + public FieldBasedTestObject() { + super(); + TARGET_FIELD = 0; + } + + public long calledFunction() { + // We put a watchpoint here and force-early-return when we are at it. + TARGET_FIELD += 10; + return TARGET_FIELD; + } + + public String toString() { + return "FieldBasedTestObject { TARGET_FIELD: " + TARGET_FIELD + " }"; + } + } + + public static class StandardTestObject extends AbstractTestObject implements Runnable { + public int cnt; + + public StandardTestObject() { + super(); + cnt = 0; + } + + public long calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and PopFrame when we are at it. + long result = cnt; // line +2 + cnt++; // line +3 + return result; // line +4 + } + + 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 long calledFunction() { + cnt++; // line +0 + // We put a breakpoint here and PopFrame when we are at it. + long result = cnt; // line +2 + cnt++; // line +3 + return result; + } + + public String toString() { + return "SynchronizedFunctionTestObject { cnt: " + cnt + " }"; + } + } + + public static class SynchronizedTestObject extends AbstractTestObject implements Runnable { + public final Object lock; + public int cnt; + + public SynchronizedTestObject() { + super(); + lock = new Object(); + cnt = 0; + } + + public long calledFunction() { + synchronized (lock) { // line +0 + cnt++; // line +1 + // We put a breakpoint here and PopFrame when we are at it. + long result = cnt; // line +3 + cnt++; // line +4 + return result; // line +5 + } + } + + 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 long calledFunction() { + cnt++; + long result = cnt; + try { + doThrow(); + cnt += 100; + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in called function."); + cnt++; + } + return result; + } + + public Object 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 long result; + + public ExceptionThrowFarTestObject(boolean catchInCalled) { + super(); + cnt = 0; + baseCallCnt = 0; + this.catchInCalled = catchInCalled; + } + + public void run() { + baseCallCnt++; + try { + result = callingFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + baseCallCnt++; + } + + public long callingFunction() { + return calledFunction(); + } + + public long calledFunction() { + cnt++; + if (catchInCalled) { + try { + cnt += 100; + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + long result = cnt; + cnt += 10; + return result; + } + } else { + cnt++; + throw new TestError(); // We put a watch here. + } + } + + public String toString() { + return "ExceptionThrowFarTestObject { cnt: " + cnt + ", baseCnt: " + baseCallCnt + " }"; + } + + @Override + public long getReturnValue() { + return result; + } + } + + 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 long calledFunction() { + cnt++; + if (cnt == 1) { + if (throwInSub) { + return doThrow(); + } else { + throw new TestError(); + } + } + return cnt++; + } + + public long 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 long getReturnValue() { + return result; + } + + public int cnt; + public int baseCallCnt; + public final boolean catchInCalled; + public long result; + + public ExceptionThrowTestObject(boolean catchInCalled) { + super(); + cnt = 0; + baseCallCnt = 0; + this.catchInCalled = catchInCalled; + } + + public void run() { + baseCallCnt++; + try { + result = calledFunction(); + } catch (TestError e) { + System.out.println(e.getClass().getName() + " thrown and caught!"); + } + baseCallCnt++; + } + + public long calledFunction() { + cnt++; + if (catchInCalled) { + try { + cnt += 10; + throw new TestError(); // We put a watch here. + } catch (TestError e) { + System.out.println(e.getClass().getName() + " caught in same function."); + long result = cnt; + cnt += 100; + return result; + } + } else { + cnt += 1; + throw new TestError(); // We put a watch here. + } + } + + public String toString() { + return "ExceptionThrowTestObject { cnt: " + cnt + ", baseCnt: " + baseCallCnt + " }"; + } + } + + public static class NativeCalledObject extends AbstractTestObject { + public int cnt = 0; + + public native long calledFunction(); + + public String toString() { + return "NativeCalledObject { cnt: " + cnt + " }"; + } + } + + public static class NativeCallerObject implements TestRunnable { + public long returnValue = -1; + public int cnt = 0; + + public long getReturnValue() { + return returnValue; + } + + public native void run(); + + public long calledFunction() { + cnt++; + // We will stop using a MethodExit event. + long res = cnt; + cnt++; + return res; + } + + public String toString() { + return "NativeCallerObject { cnt: " + cnt + " }"; + } + } + + public static class StaticMethodObject implements TestRunnable { + public int cnt = 0; + public long result = -1; + public long getReturnValue() { + return result; + } + + public static long calledFunction(Supplier<Long> incr) { + long res = incr.get().longValue(); // line +0 + // We put a breakpoint here to force the return. + doNothing(); // line +2 + incr.get(); // line +3 + return res; // line +4 + } + + public void run() { + result = calledFunction(() -> (long)++cnt); + } + + public String toString() { + return "StaticMethodObject { cnt: " + cnt + " }"; + } + } + + public static class SuspendSuddenlyObject extends AbstractTestObject { + public volatile boolean should_spin = true; + public volatile boolean is_spinning = false; + public int cnt = 0; + + public long calledFunction() { + cnt++; + do { + is_spinning = true; + } while (should_spin); + return cnt++; + } + + public String toString() { + return "SuspendSuddenlyObject { cnt: " + cnt + ", spun: " + is_spinning + " }"; + } + } + + public static class BadForceVoidObject implements TestRunnable { + public int cnt = 0; + public long getReturnValue() { + return -1; + } + public void run() { + incrCnt(); + } + public void incrCnt() { + ++cnt; // line +0 + // We set a breakpoint here and try to force-early-return. + doNothing(); // line +2 + ++cnt; // line +3 + } + public String toString() { + return "BadForceVoidObject { cnt: " + cnt + " }"; + } + } + + public static class BadForceObjectObject implements TestRunnable { + public int cnt = 0; + public Long result = null; + public long getReturnValue() { + return result.longValue(); + } + public void run() { + result = incrCnt(); + } + public Long incrCnt() { + ++cnt; // line +0 + // We set a breakpoint here and try to force-early-return. + Long res = Long.valueOf(cnt); // line +2 + ++cnt; // line +3 + return res; + } + public String toString() { + return "BadForceIntObject { cnt: " + cnt + " }"; + } + } + public static class BadForceIntObject implements TestRunnable { + public int cnt = 0; + public int result = 0; + public long getReturnValue() { + return result; + } + public void run() { + result = incrCnt(); + } + public int incrCnt() { + ++cnt; // line +0 + // We set a breakpoint here and try to force-early-return. + int res = cnt; // line +2 + ++cnt; // line +3 + return res; + } + public String toString() { + return "BadForceIntObject { cnt: " + cnt + " }"; + } + } + + public static void run() throws Exception { + new Test1970().runTests(); + } + + public static void no_runTestOn(Supplier<Object> a, ThreadRunnable b, ThreadRunnable c) {} + + public void runTests() throws Exception { + setupTest(); + + final Method calledFunction = StandardTestObject.class.getDeclaredMethod("calledFunction"); + // 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( + StandardTestObject::new, + (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr), + SuspendEvents::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( + SynchronizedFunctionTestObject::new, + (thr) -> setupSuspendBreakpointFor(syncFunctionCalledFunction, syncFunctionLoc, thr), + SuspendEvents::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( + SynchronizedTestObject::new, + (thr) -> setupSuspendBreakpointFor(syncCalledFunction, syncLoc, thr), + SuspendEvents::clearSuspendBreakpointFor); + + System.out.println("Test stopped on single step"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendSingleStepAt(calledFunction, loc, thr), + SuspendEvents::clearSuspendSingleStepFor); + + final Field target_field = FieldBasedTestObject.class.getDeclaredField("TARGET_FIELD"); + System.out.println("Test stopped on field access"); + runTestOn( + FieldBasedTestObject::new, + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, true, thr), + SuspendEvents::clearFieldSuspendFor); + + System.out.println("Test stopped on field modification"); + runTestOn( + FieldBasedTestObject::new, + (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, false, thr), + SuspendEvents::clearFieldSuspendFor); + + System.out.println("Test stopped during Method Exit of calledFunction"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendMethodEvent(calledFunction, /* enter */ false, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test stopped during Method Enter of calledFunction"); + runTestOn( + StandardTestObject::new, + (thr) -> setupSuspendMethodEvent(calledFunction, /* enter */ true, thr), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::clearSuspendMethodEvent); + + 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), + SuspendEvents::clearSuspendPopFrameEvent); + + final Method exceptionCatchThrowMethod = + ExceptionCatchTestObject.class.getDeclaredMethod("doThrow"); + System.out.println("Test stopped during notifyFramePop with exception on pop of doThrow"); + runTestOn( + ExceptionCatchTestObject::new, + (thr) -> setupSuspendPopFrameEvent(0, exceptionCatchThrowMethod, thr), + SuspendEvents::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), + SuspendEvents::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( + ExceptionCatchTestObject::new, + (thr) -> setupSuspendExceptionEvent(exceptionCatchCalledMethod, /* catch */ true, thr), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::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), + SuspendEvents::clearSuspendExceptionEvent); + + System.out.println("Test stopped during random Suspend."); + runTestOn(() -> { + final SuspendSuddenlyObject sso = new SuspendSuddenlyObject(); + return new TestConfig(sso, new TestSuspender() { + public void setupForceReturnRun(Thread thr) { } + public void setupNormalRun(Thread thr) { + sso.should_spin = false; + } + + public void waitForSuspend(Thread thr) { + while (!sso.is_spinning) { } + Suspension.suspend(thr); + } + + public void cleanup(Thread thr) { } + }); + }); + + System.out.println("Test stopped during a native method fails"); + runTestOn( + NativeCalledObject::new, + SuspendEvents::setupWaitForNativeCall, + SuspendEvents::clearWaitForNativeCall); + + System.out.println("Test stopped in a method called by native succeeds"); + final Method nativeCallerMethod = NativeCallerObject.class.getDeclaredMethod("calledFunction"); + runTestOn( + NativeCallerObject::new, + (thr) -> setupSuspendMethodEvent(nativeCallerMethod, /* enter */ false, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test stopped in a static method"); + final Method staticCalledMethod = StaticMethodObject.class.getDeclaredMethod("calledFunction", Supplier.class); + final int staticFunctionLine= Breakpoint.locationToLine(staticCalledMethod, 0) + 2; + final long staticFunctionLoc = Breakpoint.lineToLocation(staticCalledMethod, staticFunctionLine); + runTestOn( + StaticMethodObject::new, + (thr) -> setupSuspendBreakpointFor(staticCalledMethod, staticFunctionLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test force-return of void function fails!"); + final Method voidFunction = BadForceVoidObject.class.getDeclaredMethod("incrCnt"); + final int voidLine = Breakpoint.locationToLine(voidFunction, 0) + 2; + final long voidLoc = Breakpoint.lineToLocation(voidFunction, voidLine); + runTestOn( + BadForceVoidObject::new, + (thr) -> setupSuspendBreakpointFor(voidFunction, voidLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test force-return of int function fails!"); + final Method intFunction = BadForceIntObject.class.getDeclaredMethod("incrCnt"); + final int intLine = Breakpoint.locationToLine(intFunction, 0) + 2; + final long intLoc = Breakpoint.lineToLocation(intFunction, intLine); + runTestOn( + BadForceIntObject::new, + (thr) -> setupSuspendBreakpointFor(intFunction, intLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + + System.out.println("Test force-return of Object function fails!"); + final Method objFunction = BadForceObjectObject.class.getDeclaredMethod("incrCnt"); + final int objLine = Breakpoint.locationToLine(objFunction, 0) + 2; + final long objLoc = Breakpoint.lineToLocation(objFunction, objLine); + runTestOn( + BadForceObjectObject::new, + (thr) -> setupSuspendBreakpointFor(objFunction, objLoc, thr), + SuspendEvents::clearSuspendMethodEvent); + } +} diff --git a/test/1971-multi-force-early-return/expected.txt b/test/1971-multi-force-early-return/expected.txt new file mode 100644 index 0000000000..2d62363c43 --- /dev/null +++ b/test/1971-multi-force-early-return/expected.txt @@ -0,0 +1,3 @@ +Thread 0: Thread: Test1971 - Thread 0 method returned: art.Test1971$NormalExit { thread: Test1971 - Thread 0, creator: Test1971 - Thread 0 } +Thread 1: Thread: Test1971 - Thread 1 method returned: art.Test1971$ForcedExit { thread: Test1971 - Thread 1, creator: Concurrent thread force-returner - 1 } +Thread 2: Thread: Test1971 - Thread 2 method returned: art.Test1971$ForcedExit { thread: Test1971 - Thread 2, creator: Concurrent thread force-returner - 2 } diff --git a/test/1971-multi-force-early-return/info.txt b/test/1971-multi-force-early-return/info.txt new file mode 100644 index 0000000000..621d881dce --- /dev/null +++ b/test/1971-multi-force-early-return/info.txt @@ -0,0 +1,4 @@ +Test JVMTI ForceEarlyReturnObject functionality + +Checks that we can call the ForceEarlyReturn functions successfully and force +returns of objects. It also checks some of the basic error modes. diff --git a/test/1971-multi-force-early-return/run b/test/1971-multi-force-early-return/run new file mode 100755 index 0000000000..d16d4e6091 --- /dev/null +++ b/test/1971-multi-force-early-return/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/1971-multi-force-early-return/src/Main.java b/test/1971-multi-force-early-return/src/Main.java new file mode 100644 index 0000000000..a2e4fd26d3 --- /dev/null +++ b/test/1971-multi-force-early-return/src/Main.java @@ -0,0 +1,22 @@ +/* + * 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.Test1971.run(); + } +} diff --git a/test/1971-multi-force-early-return/src/art/Breakpoint.java b/test/1971-multi-force-early-return/src/art/Breakpoint.java new file mode 120000 index 0000000000..3673916cc6 --- /dev/null +++ b/test/1971-multi-force-early-return/src/art/Breakpoint.java @@ -0,0 +1 @@ +../../../jvmti-common/Breakpoint.java
\ No newline at end of file diff --git a/test/1971-multi-force-early-return/src/art/NonStandardExit.java b/test/1971-multi-force-early-return/src/art/NonStandardExit.java new file mode 120000 index 0000000000..d542a3caa7 --- /dev/null +++ b/test/1971-multi-force-early-return/src/art/NonStandardExit.java @@ -0,0 +1 @@ +../../../jvmti-common/NonStandardExit.java
\ No newline at end of file diff --git a/test/1971-multi-force-early-return/src/art/StackTrace.java b/test/1971-multi-force-early-return/src/art/StackTrace.java new file mode 120000 index 0000000000..e1a08aadbd --- /dev/null +++ b/test/1971-multi-force-early-return/src/art/StackTrace.java @@ -0,0 +1 @@ +../../../jvmti-common/StackTrace.java
\ No newline at end of file diff --git a/test/1971-multi-force-early-return/src/art/SuspendEvents.java b/test/1971-multi-force-early-return/src/art/SuspendEvents.java new file mode 120000 index 0000000000..f7a5f7e327 --- /dev/null +++ b/test/1971-multi-force-early-return/src/art/SuspendEvents.java @@ -0,0 +1 @@ +../../../jvmti-common/SuspendEvents.java
\ No newline at end of file diff --git a/test/1971-multi-force-early-return/src/art/Suspension.java b/test/1971-multi-force-early-return/src/art/Suspension.java new file mode 120000 index 0000000000..bcef96f69d --- /dev/null +++ b/test/1971-multi-force-early-return/src/art/Suspension.java @@ -0,0 +1 @@ +../../../jvmti-common/Suspension.java
\ No newline at end of file diff --git a/test/1971-multi-force-early-return/src/art/Test1971.java b/test/1971-multi-force-early-return/src/art/Test1971.java new file mode 100644 index 0000000000..0efbf9df79 --- /dev/null +++ b/test/1971-multi-force-early-return/src/art/Test1971.java @@ -0,0 +1,181 @@ +/* + * 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.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.Supplier; + +public class Test1971 { + public static final boolean PRINT_STACK_TRACE = false; + public static final int NUM_THREADS = 3; + + public static class ReturnValue { + public final Thread target; + public final Thread creator; + public final Thread.State state; + public final StackTraceElement stack[]; + + public ReturnValue(Thread thr) { + target = thr; + creator = Thread.currentThread(); + state = thr.getState(); + stack = thr.getStackTrace(); + } + + public String toString() { + String stackTrace = + PRINT_STACK_TRACE + ? ",\n\tstate: " + + state + + ",\n\tstack:\n" + + safeDumpStackTrace(stack, "\t\t") + + ",\n\t" + : ""; + return this.getClass().getName() + + " { thread: " + target.getName() + + ", creator: " + creator.getName() + + stackTrace + " }"; + } + } + + private static String safeDumpStackTrace(StackTraceElement st[], String prefix) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream os = new PrintStream(baos); + for (StackTraceElement e : st) { + os.println( + prefix + + e.getClassName() + + "." + + e.getMethodName() + + "(" + + (e.isNativeMethod() ? "Native Method" : e.getFileName()) + + ")"); + if (e.getClassName().equals("art.Test1971") && e.getMethodName().equals("runTests")) { + os.println(prefix + "<Additional frames hidden>"); + break; + } + } + os.flush(); + return baos.toString(); + } + + public static final class ForcedExit extends ReturnValue { + public ForcedExit(Thread thr) { + super(thr); + } + } + + public static final class NormalExit extends ReturnValue { + public NormalExit() { + super(Thread.currentThread()); + } + } + + public static void runTest(Consumer<String> con) { + String thread_name = Thread.currentThread().getName(); + con.accept("Thread: " + thread_name + " method returned: " + targetMethod()); + } + public static Object targetMethod() { + // Set a breakpoint here and perform a force-early-return + return new NormalExit(); + } + + public static void run() throws Exception { + SuspendEvents.setupTest(); + + final String[] results = new String[NUM_THREADS]; + final Thread[] targets = new Thread[NUM_THREADS]; + final CountDownLatch cdl = new CountDownLatch(1); + final CountDownLatch startup = new CountDownLatch(NUM_THREADS); + for (int i = 0; i < NUM_THREADS; i++) { + final int idx = i; + targets[i] = new Thread(() -> { + try { + startup.countDown(); + cdl.await(); + runTest((s) -> { + synchronized(results) { + results[idx] = s; + } + }); + } catch (Exception e) { + throw new Error("Failed to run test!", e); + } + }, "Test1971 - Thread " + i); + targets[i].start(); + } + // Wait for the targets to start. + startup.await(); + final Method targetMethod = Test1971.class.getDeclaredMethod("targetMethod"); + final long targetLoc = 0; + // Setup breakpoints on all targets. + for (Thread thr : targets) { + try { + SuspendEvents.setupSuspendBreakpointFor(targetMethod, targetLoc, thr); + } catch (RuntimeException e) { + if (e.getMessage().equals("JVMTI_ERROR_DUPLICATE")) { + continue; + } else { + throw e; + } + } + } + // Allow tests to continue. + cdl.countDown(); + // Wait for breakpoint to be hit on all threads. + for (Thread thr : targets) { + SuspendEvents.waitForSuspendHit(thr); + } + final CountDownLatch force_return_start = new CountDownLatch(NUM_THREADS); + final CountDownLatch force_return_latch = new CountDownLatch(1); + Thread[] returners = new Thread[NUM_THREADS]; + for (int i = 0; i < NUM_THREADS; i++) { + final int idx = i; + final Thread target = targets[i]; + returners[i] = new Thread(() -> { + try { + force_return_start.countDown(); + force_return_latch.await(); + if (idx % 5 != 0) { + NonStandardExit.forceEarlyReturn(target, new ForcedExit(target)); + } + Suspension.resume(target); + } catch (Exception e) { + throw new Error("Failed to resume!", e); + } + }, "Concurrent thread force-returner - " + i); + returners[i].start(); + } + // Force-early-return and resume on all threads simultaneously. + force_return_start.await(); + force_return_latch.countDown(); + + // Wait for all threads to finish. + for (int i = 0; i < NUM_THREADS; i++) { + returners[i].join(); + targets[i].join(); + } + + // Print results + for (int i = 0; i < NUM_THREADS; i++) { + System.out.println("Thread " + i + ": " + results[i]); + } + } +} diff --git a/test/Android.bp b/test/Android.bp index 76fca0b2bf..9d22847ab9 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -224,6 +224,7 @@ art_cc_defaults { "ti-agent/test_env.cc", "ti-agent/breakpoint_helper.cc", "ti-agent/common_helper.cc", + "ti-agent/early_return_helper.cc", "ti-agent/frame_pop_helper.cc", "ti-agent/locals_helper.cc", "ti-agent/monitors_helper.cc", @@ -293,8 +294,12 @@ art_cc_defaults { "1951-monitor-enter-no-suspend/raw_monitor.cc", "1953-pop-frame/pop_frame.cc", "1957-error-ext/lasterror.cc", + // TODO Renumber "1962-multi-thread-events/multi_thread_events.cc", "1963-add-to-dex-classloader-in-memory/add_to_loader.cc", + "1968-force-early-return/force_early_return.cc", + "1969-force-early-return-void/force_early_return_void.cc", + "1970-force-early-return-long/force_early_return_long.cc", ], // Use NDK-compatible headers for ctstiagent. header_libs: [ @@ -687,6 +692,10 @@ filegroup { "1953-pop-frame/src/art/Test1953.java", "1958-transform-try-jit/src/art/Test1958.java", "1962-multi-thread-events/src/art/Test1962.java", + // TODO Renumber + // "1962-force-early-return/src/art/Test1962.java", + // "1963-force-early-return-void/src/art/Test1963.java", + // "1964-force-early-return-long/src/art/Test1964.java", ], } diff --git a/test/jvmti-common/NonStandardExit.java b/test/jvmti-common/NonStandardExit.java new file mode 100644 index 0000000000..37f699e01a --- /dev/null +++ b/test/jvmti-common/NonStandardExit.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 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 NonStandardExit { + public static native void popFrame(Thread thr); + public static native void forceEarlyReturnVoid(Thread thr); + public static native void forceEarlyReturnFloat(Thread thr, float f); + public static native void forceEarlyReturnDouble(Thread thr, double f); + public static native void forceEarlyReturnInt(Thread thr, int f); + public static native void forceEarlyReturnLong(Thread thr, long f); + public static native void forceEarlyReturnObject(Thread thr, Object f); + + public static void forceEarlyReturn(Thread thr, Object o) { + if (o instanceof Number && o.getClass().getPackage().equals(Object.class.getPackage())) { + Number n = (Number)o; + if (n instanceof Integer || n instanceof Short || n instanceof Byte) { + forceEarlyReturnInt(thr, n.intValue()); + } else if (n instanceof Long) { + forceEarlyReturnLong(thr, n.longValue()); + } else if (n instanceof Float) { + forceEarlyReturnFloat(thr, n.floatValue()); + } else if (n instanceof Double) { + forceEarlyReturnDouble(thr, n.doubleValue()); + } else { + throw new IllegalArgumentException("Unknown number subtype: " + n.getClass() + " - " + n); + } + } else if (o instanceof Character) { + forceEarlyReturnInt(thr, ((Character)o).charValue()); + } else if (o instanceof Boolean) { + forceEarlyReturnInt(thr, ((Boolean)o).booleanValue() ? 1 : 0); + } else { + forceEarlyReturnObject(thr, o); + } + } +} diff --git a/test/ti-agent/early_return_helper.cc b/test/ti-agent/early_return_helper.cc new file mode 100644 index 0000000000..e4aa5d0961 --- /dev/null +++ b/test/ti-agent/early_return_helper.cc @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 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 "common_helper.h" + +#include "jni.h" +#include "jvmti.h" + +#include "jvmti_helper.h" +#include "scoped_local_ref.h" +#include "test_env.h" + +namespace art { +namespace common_early_return { + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_popFrame( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->PopFrame(thr)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_forceEarlyReturnFloat( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr, jfloat val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->ForceEarlyReturnFloat(thr, val)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_forceEarlyReturnDouble( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr, jdouble val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->ForceEarlyReturnDouble(thr, val)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_forceEarlyReturnLong( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr, jlong val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->ForceEarlyReturnLong(thr, val)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_forceEarlyReturnInt( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr, jint val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->ForceEarlyReturnInt(thr, val)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_forceEarlyReturnVoid( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->ForceEarlyReturnVoid(thr)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_NonStandardExit_forceEarlyReturnObject( + JNIEnv* env, jclass k ATTRIBUTE_UNUSED, jthread thr, jobject val) { + JvmtiErrorToException(env, jvmti_env, jvmti_env->ForceEarlyReturnObject(thr, val)); +} + +} // namespace common_early_return +} // namespace art diff --git a/test/ti-agent/suspend_event_helper.cc b/test/ti-agent/suspend_event_helper.cc index 29d94b1b2d..cbc54d4c6d 100644 --- a/test/ti-agent/suspend_event_helper.cc +++ b/test/ti-agent/suspend_event_helper.cc @@ -286,6 +286,7 @@ extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupTest(JNIEnv* env, 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_force_early_return = 1; caps.can_generate_single_step_events = 1; caps.can_generate_breakpoint_events = 1; caps.can_suspend = 1; diff --git a/tools/tracefast-plugin/tracefast.cc b/tools/tracefast-plugin/tracefast.cc index 98f7ea5037..45dfe5f1c6 100644 --- a/tools/tracefast-plugin/tracefast.cc +++ b/tools/tracefast-plugin/tracefast.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "android-base/macros.h" #include "gc/scoped_gc_critical_section.h" #include "instrumentation.h" #include "runtime.h" @@ -52,14 +53,16 @@ class Tracer final : public art::instrumentation::InstrumentationListener { art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, art::ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, - art::Handle<art::mirror::Object> return_value ATTRIBUTE_UNUSED) + art::instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED, + art::MutableHandle<art::mirror::Object>& return_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(art::Locks::mutator_lock_) { } void MethodExited(art::Thread* thread ATTRIBUTE_UNUSED, art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, art::ArtMethod* method ATTRIBUTE_UNUSED, uint32_t dex_pc ATTRIBUTE_UNUSED, - const art::JValue& return_value ATTRIBUTE_UNUSED) + art::instrumentation::OptionalFrame frame ATTRIBUTE_UNUSED, + art::JValue& return_value ATTRIBUTE_UNUSED) override REQUIRES_SHARED(art::Locks::mutator_lock_) { } void MethodUnwind(art::Thread* thread ATTRIBUTE_UNUSED, |