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