JVMTI Force early return
Add support for can_force_early_return jvmti capability. This allows
one to force java frames to exit early. Exited frames have all of
their normal locks released.
We implement this by modifying the existing method exit events to
allow one to modify the exit value during the callback. This is used
to implement ForceEarlyReturn by adding internal-only events that will
change the return value of methods once they return (using
kForcePopFrame) avoiding the need to modify the actual interpreter
very deeply. This also makes it simple to continue to use the standard
deoptimization functions to force the actual return.
In order to simplify book-keeping the internal event is refcounted,
not associated with any specific jvmtiEnv, and only settable on
specific threads. The internal event is added by the ForceEarlyReturn
function and then removed by the MethodExit event when we update the
return value.
Bug: 130028055
Test: ./test.py --host
Change-Id: Ifa44605b4e8032605f503a654ddf4bd2fc6b60bf
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index e889f98..e4ce825 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -344,50 +344,40 @@
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 7433e54..083ba6d 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -278,7 +278,7 @@
.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 @@
// 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 @@
.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 8e06fe3..627129a 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -362,7 +362,7 @@
// 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 @@
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 @@
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 40e8b80..7174e1b 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 @@
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 @@
}
}
+ // 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 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 @@
return ERR(ILLEGAL_ARGUMENT);
}
- if (!EventMask::EventIsInRange(event)) {
+ if (!EventIsNormal(event)) {
return ERR(INVALID_EVENT_TYPE);
}
@@ -1385,6 +1594,46 @@
}
}
+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 @@
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 d54c87a..ac86d0c 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
// 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 75f0556..38257f1 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 @@
return OK;
}
+namespace {
+
+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::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!";
+ }
+
+ 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;
+ }
+}
+
+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;
+ }
+}
+
+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);
+ }
+ }
+
+ 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);
+}
+
+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();
- art::Thread* target;
-
- 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)) {
+ NonStandardExitFrames<NonStandardExitType::kPopFrame> frames(self, env, thread);
+ if (frames.result_ != OK) {
art::Locks::thread_list_lock_->ExclusiveUnlock(self);
- return err;
+ return frames.result_;
}
- {
- 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);
- }
- art::Locks::thread_suspend_count_lock_->ExclusiveUnlock(self);
- }
- 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);
- }
- // 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);
- }
-
- 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);
- }
- // From here we are sure to succeed.
-
- // Get/create a shadow frame
- bool created_final_frame = false;
- bool created_penultimate_frame = false;
- art::ShadowFrame* called_shadow_frame =
- final_frame.GetOrCreateShadowFrame(&created_final_frame);
- art::ShadowFrame* calling_shadow_frame =
- penultimate_frame.GetOrCreateShadowFrame(&created_penultimate_frame);
-
- CHECK_NE(called_shadow_frame, calling_shadow_frame)
- << "Frames at different depths not different!";
-
// Tell the shadow-frame to return immediately and skip all exit events.
- called_shadow_frame->SetForcePopFrame(true);
- calling_shadow_frame->SetForceRetryInstruction(true);
-
- // Make sure can we will go to the interpreter and use the shadow frames. The
- // early return for the final frame will force everything to the interpreter
- // so we only need to instrument if it was not present.
- if (created_final_frame) {
- art::FunctionClosure fc([](art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ 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);
});
- target->RequestSynchronousCheckpoint(&fc);
+ frames.target_->RequestSynchronousCheckpoint(&fc);
} else {
art::Locks::thread_list_lock_->ExclusiveUnlock(self);
}
return OK;
}
+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);
+ });
+ 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 55c4269..918aa4c 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 @@
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 6c50a20..f2ae996 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -229,6 +229,7 @@
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 c5443bf..5bf8a3f 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 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 a749106..2f86fbc 100644
--- a/runtime/common_dex_operations.h
+++ b/runtime/common_dex_operations.h
@@ -179,7 +179,6 @@
// 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 02fb5b6..914a750 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 @@
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 5dbdf41..9971f4a 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -965,6 +965,7 @@
soa.Decode<mirror::Object>(rcvr_jobj),
proxy_method,
0,
+ {},
result);
}
return result.GetJ();
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 984f947..fc2143f 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 @@
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 @@
!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 @@
}
}
+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 @@
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 fc64c49..a7907c8 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 @@
// 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 @@
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 @@
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 @@
}
// 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 @@
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 cf5d3ed..6284299 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 @@
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 @@
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 @@
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 @@
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 ce242a7..5f176db 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -275,17 +275,38 @@
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 4e08e8c..9953743 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 @@
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 @@
#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 @@
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 @@
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 19da77d..752965f 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 @@
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 a487cd6..085c475 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 3f6b729..8981816 100644
--- a/runtime/interpreter/shadow_frame.h
+++ b/runtime/interpreter/shadow_frame.h
@@ -57,6 +57,9 @@
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 @@
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 fbbcc90..3e83f65 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2141,20 +2141,7 @@
// 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 @@
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 @@
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 c4a9bcf..8fe9466 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 @@
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 0c31faa..faea146 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 @@
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 567f6ed..eccf157 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -185,7 +185,8 @@
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 0000000..bd38590
--- /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 0000000..6742165
--- /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 0000000..621d881
--- /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 0000000..d16d4e6
--- /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 0000000..2aa26bf
--- /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 0000000..3673916
--- /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 0000000..d542a3c
--- /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 0000000..e1a08aa
--- /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 0000000..f7a5f7e
--- /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 0000000..bcef96f
--- /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 0000000..a6aea86
--- /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 0000000..d552272
--- /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 0000000..5e13595
--- /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 0000000..fc685b4
--- /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 0000000..2935362
--- /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 0000000..19fdb1a
--- /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 0000000..e92b873
--- /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 0000000..e37c910
--- /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 0000000..3673916
--- /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 0000000..d542a3c
--- /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 0000000..e1a08aa
--- /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 0000000..f7a5f7e
--- /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 0000000..bcef96f
--- /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 0000000..898da27
--- /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 0000000..ab79587
--- /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 0000000..da0c946
--- /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 0000000..621d881
--- /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 0000000..d16d4e6
--- /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 0000000..5a75458
--- /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 0000000..3673916
--- /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 0000000..d542a3c
--- /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 0000000..e1a08aa
--- /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 0000000..f7a5f7e
--- /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 0000000..bcef96f
--- /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 0000000..976d4e9
--- /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 0000000..2d62363
--- /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 0000000..621d881
--- /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 0000000..d16d4e6
--- /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 0000000..a2e4fd2
--- /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 0000000..3673916
--- /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 0000000..d542a3c
--- /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 0000000..e1a08aa
--- /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 0000000..f7a5f7e
--- /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 0000000..bcef96f
--- /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 0000000..0efbf9d
--- /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 76fca0b..9d22847 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -224,6 +224,7 @@
"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 @@
"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 @@
"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 0000000..37f699e
--- /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 0000000..e4aa5d0
--- /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 29d94b1..cbc54d4 100644
--- a/test/ti-agent/suspend_event_helper.cc
+++ b/test/ti-agent/suspend_event_helper.cc
@@ -286,6 +286,7 @@
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 98f7ea5..45dfe5f 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 @@
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,