summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alex Light <allight@google.com> 2017-11-17 16:39:01 -0800
committer Alex Light <allight@google.com> 2017-11-20 10:58:30 -0800
commite5a2ae30bdbe379695dc886861b23dce57de0825 (patch)
tree41efa0e726e23607557fe7d8bbc03854cf06ea0f
parent8acd00f9e65078f0f9d3c6fa7fea19ee0c17567a (diff)
Make JVMTI DisposeEnvironment and GetEnv thread safe.
Previously we were relying on the mutator lock to keep these safe but it turns out this was not sufficient. We give the list of active jvmtiEnv's it's own lock to synchronize access. We also changed it so that during events we would collect all the environments and callbacks prior to actually calling any of them. This is required for making sure that we don't hold locks across user code or potentially miss any environments. This does have implications for when one is last able to prevent an environment from getting an event but since the spec is vague about this anyway this is not an issue. Doing this required a major re-write of our event-dispatch system. Test: ./test.py --host -j50 Test: ./art/tools/run-libjdwp-tests.sh --mode=host Bug: 69465262 Change-Id: I170950db6c6e43b5f3c8bdca1b8d087937070496
-rw-r--r--openjdkjvmti/art_jvmti.h4
-rw-r--r--openjdkjvmti/events-inl.h277
-rw-r--r--openjdkjvmti/events.cc21
-rw-r--r--openjdkjvmti/events.h67
-rw-r--r--openjdkjvmti/object_tagging.cc3
-rw-r--r--openjdkjvmti/ti_dump.cc2
-rw-r--r--openjdkjvmti/ti_phase.cc11
-rw-r--r--runtime/base/mutex-inl.h4
-rw-r--r--runtime/base/mutex.h5
-rw-r--r--runtime/ti/agent.cc3
-rw-r--r--test/1941-dispose-stress/dispose_stress.cc59
-rw-r--r--test/1941-dispose-stress/expected.txt1
-rw-r--r--test/1941-dispose-stress/info.txt3
-rwxr-xr-xtest/1941-dispose-stress/run18
-rw-r--r--test/1941-dispose-stress/src/Main.java21
-rw-r--r--test/1941-dispose-stress/src/art/Breakpoint.java202
-rw-r--r--test/1941-dispose-stress/src/art/Test1941.java72
-rw-r--r--test/1941-dispose-stress/src/art/Trace.java68
-rw-r--r--test/Android.bp1
19 files changed, 700 insertions, 142 deletions
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 682b82b5cd..e8e62c2b40 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -94,6 +94,10 @@ struct ArtJvmTiEnv : public jvmtiEnv {
static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) {
return art::down_cast<ArtJvmTiEnv*>(env);
}
+
+ // Top level lock. Nothing can be held when we get this except for mutator lock for full
+ // thread-suspension.
+ static art::Mutex *gEnvMutex ACQUIRED_AFTER(art::Locks::mutator_lock_);
};
// Macro and constexpr to make error values less annoying to write.
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 5344e0fbde..007669b50f 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -46,6 +46,45 @@ static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) {
namespace impl {
+// Helper for ensuring that the dispatch environment is sane. Events with JNIEnvs need to stash
+// pending exceptions since they can cause new ones to be thrown. In accordance with the JVMTI
+// specification we allow exceptions originating from events to overwrite the current exception,
+// including exceptions originating from earlier events.
+class ScopedEventDispatchEnvironment FINAL : public art::ValueObject {
+ public:
+ ScopedEventDispatchEnvironment() : env_(nullptr), throw_(nullptr, nullptr) {
+ DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative);
+ }
+
+ explicit ScopedEventDispatchEnvironment(JNIEnv* env)
+ : env_(env),
+ throw_(env_, env_->ExceptionOccurred()) {
+ DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative);
+ // The spec doesn't say how much local data should be there, so we just give 128 which seems
+ // likely to be enough for most cases.
+ env_->PushLocalFrame(128);
+ env_->ExceptionClear();
+ }
+
+ ~ScopedEventDispatchEnvironment() {
+ if (env_ != nullptr) {
+ if (throw_.get() != nullptr && !env_->ExceptionCheck()) {
+ // TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list
+ // of the newest exception.
+ env_->Throw(throw_.get());
+ }
+ env_->PopLocalFrame(nullptr);
+ }
+ DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative);
+ }
+
+ private:
+ JNIEnv* env_;
+ ScopedLocalRef<jthrowable> throw_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatchEnvironment);
+};
+
// Infrastructure to achieve type safety for event dispatch.
#define FORALL_EVENT_TYPES(fn) \
@@ -97,27 +136,68 @@ FORALL_EVENT_TYPES(EVENT_FN_TYPE)
#undef EVENT_FN_TYPE
-template <ArtJvmtiEvent kEvent>
-ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env);
-
-#define GET_CALLBACK(name, enum_name) \
-template <> \
-ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \
- ArtJvmTiEnv* env) { \
- if (env->event_callbacks == nullptr) { \
- return nullptr; \
- } \
- return env->event_callbacks->name; \
-}
+#define MAKE_EVENT_HANDLER_FUNC(name, enum_name) \
+template<> \
+struct EventHandlerFunc<enum_name> { \
+ using EventFnType = typename impl::EventFnType<enum_name>::type; \
+ explicit EventHandlerFunc(ArtJvmTiEnv* env) \
+ : env_(env), \
+ fn_(env_->event_callbacks == nullptr ? nullptr : env_->event_callbacks->name) { } \
+ \
+ template <typename ...Args> \
+ ALWAYS_INLINE \
+ void ExecuteCallback(JNIEnv* jnienv, Args... args) const { \
+ if (fn_ != nullptr) { \
+ ScopedEventDispatchEnvironment sede(jnienv); \
+ DoExecute(jnienv, args...); \
+ } \
+ } \
+ \
+ template <typename ...Args> \
+ ALWAYS_INLINE \
+ void ExecuteCallback(Args... args) const { \
+ if (fn_ != nullptr) { \
+ ScopedEventDispatchEnvironment sede; \
+ DoExecute(args...); \
+ } \
+ } \
+ \
+ private: \
+ template <typename ...Args> \
+ ALWAYS_INLINE \
+ inline void DoExecute(Args... args) const { \
+ static_assert(std::is_same<EventFnType, void(*)(jvmtiEnv*, Args...)>::value, \
+ "Unexpected different type of ExecuteCallback"); \
+ fn_(env_, args...); \
+ } \
+ \
+ public: \
+ ArtJvmTiEnv* env_; \
+ EventFnType fn_; \
+};
-FORALL_EVENT_TYPES(GET_CALLBACK)
+FORALL_EVENT_TYPES(MAKE_EVENT_HANDLER_FUNC)
-#undef GET_CALLBACK
+#undef MAKE_EVENT_HANDLER_FUNC
#undef FORALL_EVENT_TYPES
} // namespace impl
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline std::vector<impl::EventHandlerFunc<kEvent>> EventHandler::CollectEvents(art::Thread* thread,
+ Args... args) const {
+ art::MutexLock mu(thread, envs_lock_);
+ std::vector<impl::EventHandlerFunc<kEvent>> handlers;
+ for (ArtJvmTiEnv* env : envs) {
+ if (ShouldDispatch<kEvent>(env, thread, args...)) {
+ impl::EventHandlerFunc<kEvent> h(env);
+ handlers.push_back(h);
+ }
+ }
+ return handlers;
+}
+
// C++ does not allow partial template function specialization. The dispatch for our separated
// ClassFileLoadHook event types is the same, so use this helper for code deduplication.
template <ArtJvmtiEvent kEvent>
@@ -131,29 +211,37 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
const unsigned char* class_data,
jint* new_class_data_len,
unsigned char** new_class_data) const {
+ art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
DCHECK(*new_class_data == nullptr);
jint current_len = class_data_len;
unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
+ std::vector<impl::EventHandlerFunc<kEvent>> handlers =
+ CollectEvents<kEvent>(thread,
+ jnienv,
+ class_being_redefined,
+ loader,
+ name,
+ protection_domain,
+ class_data_len,
+ class_data,
+ new_class_data_len,
+ new_class_data);
ArtJvmTiEnv* last_env = nullptr;
- for (ArtJvmTiEnv* env : envs) {
- if (env == nullptr) {
- continue;
- }
+ for (const impl::EventHandlerFunc<kEvent>& event : handlers) {
jint new_len = 0;
unsigned char* new_data = nullptr;
- DispatchEventOnEnv<kEvent>(env,
- thread,
- jnienv,
- class_being_redefined,
- loader,
- name,
- protection_domain,
- current_len,
- static_cast<const unsigned char*>(current_class_data),
- &new_len,
- &new_data);
+ ExecuteCallback<kEvent>(event,
+ jnienv,
+ class_being_redefined,
+ loader,
+ name,
+ protection_domain,
+ current_len,
+ static_cast<const unsigned char*>(current_class_data),
+ &new_len,
+ &new_data);
if (new_data != nullptr && new_data != current_class_data) {
// Destroy the data the last transformer made. We skip this if the previous state was the
// initial one since we don't know here which jvmtiEnv allocated it.
@@ -162,7 +250,7 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
if (last_env != nullptr) {
last_env->Deallocate(current_class_data);
}
- last_env = env;
+ last_env = event.env_;
current_class_data = new_data;
current_len = new_len;
}
@@ -177,69 +265,27 @@ inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
// exactly the argument types of the corresponding Jvmti kEvent function pointer.
template <ArtJvmtiEvent kEvent, typename ...Args>
-inline void EventHandler::ExecuteCallback(ArtJvmTiEnv* env, Args... args) {
- using FnType = typename impl::EventFnType<kEvent>::type;
- FnType callback = impl::GetCallback<kEvent>(env);
- if (callback != nullptr) {
- (*callback)(env, args...);
- }
-}
-
-template <ArtJvmtiEvent kEvent, typename ...Args>
inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const {
+ art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
static_assert(!std::is_same<JNIEnv*,
typename std::decay_t<
std::tuple_element_t<0, std::tuple<Args..., nullptr_t>>>>::value,
"Should be calling DispatchEvent with explicit JNIEnv* argument!");
DCHECK(thread == nullptr || !thread->IsExceptionPending());
- for (ArtJvmTiEnv* env : envs) {
- if (env != nullptr) {
- DispatchEventOnEnv<kEvent, Args...>(env, thread, args...);
- }
+ std::vector<impl::EventHandlerFunc<kEvent>> events = CollectEvents<kEvent>(thread, args...);
+ for (auto event : events) {
+ ExecuteCallback<kEvent>(event, args...);
}
}
-// Helper for ensuring that the dispatch environment is sane. Events with JNIEnvs need to stash
-// pending exceptions since they can cause new ones to be thrown. In accordance with the JVMTI
-// specification we allow exceptions originating from events to overwrite the current exception,
-// including exceptions originating from earlier events.
-class ScopedEventDispatchEnvironment FINAL : public art::ValueObject {
- public:
- explicit ScopedEventDispatchEnvironment(JNIEnv* env)
- : env_(env),
- thr_(env_, env_->ExceptionOccurred()),
- suspend_(art::Thread::Current(), art::kNative) {
- // The spec doesn't say how much local data should be there, so we just give 128 which seems
- // likely to be enough for most cases.
- env_->PushLocalFrame(128);
- env_->ExceptionClear();
- UNUSED(suspend_);
- }
-
- ~ScopedEventDispatchEnvironment() {
- if (thr_.get() != nullptr && !env_->ExceptionCheck()) {
- // TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list
- // of the newest exception.
- env_->Throw(thr_.get());
- }
- env_->PopLocalFrame(nullptr);
- }
-
- private:
- JNIEnv* env_;
- ScopedLocalRef<jthrowable> thr_;
- // Not actually unused. The destructor/constructor does important work.
- art::ScopedThreadStateChange suspend_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatchEnvironment);
-};
-
template <ArtJvmtiEvent kEvent, typename ...Args>
inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const {
- for (ArtJvmTiEnv* env : envs) {
- if (env != nullptr) {
- DispatchEventOnEnv<kEvent, Args...>(env, thread, jnienv, args...);
- }
+ art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
+ std::vector<impl::EventHandlerFunc<kEvent>> events = CollectEvents<kEvent>(thread,
+ jnienv,
+ args...);
+ for (auto event : events) {
+ ExecuteCallback<kEvent>(event, jnienv, args...);
}
}
@@ -248,8 +294,9 @@ inline void EventHandler::DispatchEventOnEnv(
ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const {
DCHECK(env != nullptr);
if (ShouldDispatch<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...)) {
- ScopedEventDispatchEnvironment sede(jnienv);
- ExecuteCallback<kEvent, JNIEnv*, Args...>(env, jnienv, args...);
+ art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
+ impl::EventHandlerFunc<kEvent> func(env);
+ ExecuteCallback<kEvent>(func, jnienv, args...);
}
}
@@ -260,11 +307,26 @@ inline void EventHandler::DispatchEventOnEnv(
typename std::decay_t<
std::tuple_element_t<0, std::tuple<Args..., nullptr_t>>>>::value,
"Should be calling DispatchEventOnEnv with explicit JNIEnv* argument!");
- if (ShouldDispatch<kEvent>(env, thread, args...)) {
- ExecuteCallback<kEvent, Args...>(env, args...);
+ DCHECK(env != nullptr);
+ if (ShouldDispatch<kEvent, Args...>(env, thread, args...)) {
+ art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
+ impl::EventHandlerFunc<kEvent> func(env);
+ ExecuteCallback<kEvent>(func, args...);
}
}
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc<kEvent> handler, Args... args) {
+ handler.ExecuteCallback(args...);
+}
+
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc<kEvent> handler,
+ JNIEnv* jnienv,
+ Args... args) {
+ handler.ExecuteCallback(jnienv, args...);
+}
+
// Events that need custom logic for if we send the event but are otherwise normal. This includes
// the kBreakpoint, kFramePop, kFieldAccess, and kFieldModification events.
@@ -347,14 +409,13 @@ inline bool EventHandler::ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(
// something.
template <>
inline void EventHandler::ExecuteCallback<ArtJvmtiEvent::kFramePop>(
- ArtJvmTiEnv* env,
+ impl::EventHandlerFunc<ArtJvmtiEvent::kFramePop> event,
JNIEnv* jnienv,
jthread jni_thread,
jmethodID jmethod,
jboolean is_exception,
const art::ShadowFrame* frame ATTRIBUTE_UNUSED) {
- ExecuteCallback<ArtJvmtiEvent::kFramePop>(
- env, jnienv, jni_thread, jmethod, is_exception);
+ ExecuteCallback<ArtJvmtiEvent::kFramePop>(event, jnienv, jni_thread, jmethod, is_exception);
}
// Need to give a custom specialization for NativeMethodBind since it has to deal with an out
@@ -366,20 +427,25 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::T
jmethodID method,
void* cur_method,
void** new_method) const {
+ art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
+ std::vector<impl::EventHandlerFunc<ArtJvmtiEvent::kNativeMethodBind>> events =
+ CollectEvents<ArtJvmtiEvent::kNativeMethodBind>(thread,
+ jnienv,
+ jni_thread,
+ method,
+ cur_method,
+ new_method);
*new_method = cur_method;
- for (ArtJvmTiEnv* env : envs) {
- if (env != nullptr) {
- *new_method = cur_method;
- DispatchEventOnEnv<ArtJvmtiEvent::kNativeMethodBind>(env,
- thread,
- jnienv,
- jni_thread,
- method,
- cur_method,
- new_method);
- if (*new_method != nullptr) {
- cur_method = *new_method;
- }
+ for (auto event : events) {
+ *new_method = cur_method;
+ ExecuteCallback<ArtJvmtiEvent::kNativeMethodBind>(event,
+ jnienv,
+ jni_thread,
+ method,
+ cur_method,
+ new_method);
+ if (*new_method != nullptr) {
+ cur_method = *new_method;
}
}
*new_method = cur_method;
@@ -439,7 +505,7 @@ inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetr
}
template <ArtJvmtiEvent kEvent>
-inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) {
+inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const {
bool dispatch = env->event_masks.global_event_mask.Test(kEvent);
if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) {
@@ -461,6 +527,11 @@ inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env,
}
inline void EventHandler::RecalculateGlobalEventMask(ArtJvmtiEvent event) {
+ art::MutexLock mu(art::Thread::Current(), envs_lock_);
+ RecalculateGlobalEventMaskLocked(event);
+}
+
+inline void EventHandler::RecalculateGlobalEventMaskLocked(ArtJvmtiEvent event) {
bool union_value = false;
for (const ArtJvmTiEnv* stored_env : envs) {
if (stored_env == nullptr) {
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index d1d606de48..be4ebbc85e 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -193,25 +193,21 @@ void EventMasks::HandleChangedCapabilities(const jvmtiCapabilities& caps, bool c
}
void EventHandler::RegisterArtJvmTiEnv(ArtJvmTiEnv* env) {
- // Since we never shrink this array we might as well try to fill gaps.
- auto it = std::find(envs.begin(), envs.end(), nullptr);
- if (it != envs.end()) {
- *it = env;
- } else {
- envs.push_back(env);
- }
+ art::MutexLock mu(art::Thread::Current(), envs_lock_);
+ envs.push_back(env);
}
void EventHandler::RemoveArtJvmTiEnv(ArtJvmTiEnv* env) {
+ art::MutexLock mu(art::Thread::Current(), envs_lock_);
// Since we might be currently iterating over the envs list we cannot actually erase elements.
// Instead we will simply replace them with 'nullptr' and skip them manually.
auto it = std::find(envs.begin(), envs.end(), env);
if (it != envs.end()) {
- *it = nullptr;
+ envs.erase(it);
for (size_t i = static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal);
i <= static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal);
++i) {
- RecalculateGlobalEventMask(static_cast<ArtJvmtiEvent>(i));
+ RecalculateGlobalEventMaskLocked(static_cast<ArtJvmtiEvent>(i));
}
}
}
@@ -431,11 +427,11 @@ class JvmtiGcPauseListener : public art::gc::GcPauseListener {
finish_enabled_(false) {}
void StartPause() OVERRIDE {
- handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr);
+ handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(art::Thread::Current());
}
void EndPause() OVERRIDE {
- handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr);
+ handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(art::Thread::Current());
}
bool IsEnabled() {
@@ -1176,7 +1172,8 @@ void EventHandler::Shutdown() {
art::Runtime::Current()->GetInstrumentation()->RemoveListener(method_trace_listener_.get(), ~0);
}
-EventHandler::EventHandler() {
+EventHandler::EventHandler() : envs_lock_("JVMTI Environment List Lock",
+ art::LockLevel::kTopLockLevel) {
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 a99ed7b212..c73215f07b 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -158,6 +158,10 @@ struct EventMasks {
void HandleChangedCapabilities(const jvmtiCapabilities& caps, bool caps_added);
};
+namespace impl {
+template <ArtJvmtiEvent kEvent> struct EventHandlerFunc { };
+} // namespace impl
+
// Helper class for event handling.
class EventHandler {
public:
@@ -169,10 +173,10 @@ class EventHandler {
// Register an env. It is assumed that this happens on env creation, that is, no events are
// enabled, yet.
- void RegisterArtJvmTiEnv(ArtJvmTiEnv* env);
+ void RegisterArtJvmTiEnv(ArtJvmTiEnv* env) REQUIRES(!envs_lock_);
// Remove an env.
- void RemoveArtJvmTiEnv(ArtJvmTiEnv* env);
+ void RemoveArtJvmTiEnv(ArtJvmTiEnv* env) REQUIRES(!envs_lock_);
bool IsEventEnabledAnywhere(ArtJvmtiEvent event) const {
if (!EventMask::EventIsInRange(event)) {
@@ -184,13 +188,15 @@ class EventHandler {
jvmtiError SetEvent(ArtJvmTiEnv* env,
art::Thread* thread,
ArtJvmtiEvent event,
- jvmtiEventMode mode);
+ jvmtiEventMode mode)
+ REQUIRES(!envs_lock_);
// Dispatch event to all registered environments. Since this one doesn't have a JNIEnv* it doesn't
// matter if it has the mutator_lock.
template <ArtJvmtiEvent kEvent, typename ...Args>
ALWAYS_INLINE
- inline void DispatchEvent(art::Thread* thread, Args... args) const;
+ inline void DispatchEvent(art::Thread* thread, Args... args) const
+ REQUIRES(!envs_lock_);
// Dispatch event to all registered environments stashing exceptions as needed. This works since
// JNIEnv* is always the second argument if it is passed to an event. Needed since C++ does not
@@ -200,7 +206,8 @@ class EventHandler {
// the event to allocate local references.
template <ArtJvmtiEvent kEvent, typename ...Args>
ALWAYS_INLINE
- inline void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const;
+ inline void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const
+ REQUIRES(!envs_lock_);
// Tell the event handler capabilities were added/lost so it can adjust the sent events.If
// caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false
@@ -208,30 +215,50 @@ class EventHandler {
ALWAYS_INLINE
inline void HandleChangedCapabilities(ArtJvmTiEnv* env,
const jvmtiCapabilities& caps,
- bool added);
+ bool added)
+ REQUIRES(!envs_lock_);
// Dispatch event to the given environment, only.
template <ArtJvmtiEvent kEvent, typename ...Args>
ALWAYS_INLINE
- inline void DispatchEventOnEnv(
- ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const;
+ inline void DispatchEventOnEnv(ArtJvmTiEnv* env,
+ art::Thread* thread,
+ JNIEnv* jnienv,
+ Args... args) const
+ REQUIRES(!envs_lock_);
// Dispatch event to the given environment, only.
template <ArtJvmtiEvent kEvent, typename ...Args>
ALWAYS_INLINE
- inline void DispatchEventOnEnv(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const;
+ inline void DispatchEventOnEnv(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const
+ REQUIRES(!envs_lock_);
private:
+ template <ArtJvmtiEvent kEvent, typename ...Args>
+ ALWAYS_INLINE
+ inline std::vector<impl::EventHandlerFunc<kEvent>> CollectEvents(art::Thread* thread,
+ Args... args) const
+ REQUIRES(!envs_lock_);
+
template <ArtJvmtiEvent kEvent>
ALWAYS_INLINE
- static inline bool ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread);
+ inline bool ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const;
template <ArtJvmtiEvent kEvent, typename ...Args>
ALWAYS_INLINE
- static inline void ExecuteCallback(ArtJvmTiEnv* env, Args... args);
+ static inline void ExecuteCallback(impl::EventHandlerFunc<kEvent> handler,
+ JNIEnv* env,
+ Args... args)
+ REQUIRES(!envs_lock_);
template <ArtJvmtiEvent kEvent, typename ...Args>
ALWAYS_INLINE
+ static inline void ExecuteCallback(impl::EventHandlerFunc<kEvent> handler, Args... args)
+ REQUIRES(!envs_lock_);
+
+ // Public for use to collect dispatches
+ template <ArtJvmtiEvent kEvent, typename ...Args>
+ ALWAYS_INLINE
inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const;
ALWAYS_INLINE
@@ -241,7 +268,9 @@ class EventHandler {
// Recalculates the event mask for the given event.
ALWAYS_INLINE
- inline void RecalculateGlobalEventMask(ArtJvmtiEvent event);
+ inline void RecalculateGlobalEventMask(ArtJvmtiEvent event) REQUIRES(!envs_lock_);
+ ALWAYS_INLINE
+ inline void RecalculateGlobalEventMaskLocked(ArtJvmtiEvent event) REQUIRES(envs_lock_);
template <ArtJvmtiEvent kEvent>
ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread,
@@ -253,7 +282,8 @@ class EventHandler {
jint class_data_len,
const unsigned char* class_data,
jint* new_class_data_len,
- unsigned char** new_class_data) const;
+ unsigned char** new_class_data) const
+ REQUIRES(!envs_lock_);
void HandleEventType(ArtJvmtiEvent event, bool enable);
void HandleLocalAccessCapabilityAdded();
@@ -261,10 +291,13 @@ class EventHandler {
bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event);
- // List of all JvmTiEnv objects that have been created, in their creation order.
- // NB Some elements might be null representing envs that have been deleted. They should be skipped
- // anytime this list is used.
- std::vector<ArtJvmTiEnv*> envs;
+ // 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.
+ std::list<ArtJvmTiEnv*> envs GUARDED_BY(envs_lock_);
+
+ // Top level lock. Nothing at all should be held when we lock this.
+ mutable art::Mutex envs_lock_ ACQUIRED_BEFORE(art::Locks::instrument_entrypoints_lock_);
// A union of all enabled events, anywhere.
EventMask global_mask;
diff --git a/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc
index 6ba7165577..ba242ef1e8 100644
--- a/openjdkjvmti/object_tagging.cc
+++ b/openjdkjvmti/object_tagging.cc
@@ -61,7 +61,8 @@ bool ObjectTagTable::DoesHandleNullOnSweep() {
return event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kObjectFree);
}
void ObjectTagTable::HandleNullSweep(jlong tag) {
- event_handler_->DispatchEventOnEnv<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag);
+ event_handler_->DispatchEventOnEnv<ArtJvmtiEvent::kObjectFree>(
+ jvmti_env_, art::Thread::Current(), tag);
}
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_dump.cc b/openjdkjvmti/ti_dump.cc
index 809a5e47bb..253580e0e1 100644
--- a/openjdkjvmti/ti_dump.cc
+++ b/openjdkjvmti/ti_dump.cc
@@ -47,7 +47,7 @@ struct DumpCallback : public art::RuntimeSigQuitCallback {
void SigQuit() OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
art::Thread* thread = art::Thread::Current();
art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
- event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr);
+ event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(art::Thread::Current());
}
EventHandler* event_handler = nullptr;
diff --git a/openjdkjvmti/ti_phase.cc b/openjdkjvmti/ti_phase.cc
index 23df27fbda..7157974c13 100644
--- a/openjdkjvmti/ti_phase.cc
+++ b/openjdkjvmti/ti_phase.cc
@@ -57,6 +57,7 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
}
void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+ art::Thread* self = art::Thread::Current();
switch (phase) {
case RuntimePhase::kInitialAgents:
PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
@@ -64,8 +65,7 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
case RuntimePhase::kStart:
{
PhaseUtil::current_phase_ = JVMTI_PHASE_START;
- art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
- event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv());
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(self, GetJniEnv());
}
break;
case RuntimePhase::kInit:
@@ -74,9 +74,7 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
{
ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
- art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
- event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(
- nullptr, GetJniEnv(), thread.get());
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(self, GetJniEnv(), thread.get());
}
// We need to have these events be ordered to match behavior expected by some real-world
// agents. The spec does not really require this but compatibility is a useful property to
@@ -86,8 +84,7 @@ struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
break;
case RuntimePhase::kDeath:
{
- art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
- event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv());
+ event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(self, GetJniEnv());
PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
}
// TODO: Block events now.
diff --git a/runtime/base/mutex-inl.h b/runtime/base/mutex-inl.h
index c9d48ff7f7..39ad3d0c89 100644
--- a/runtime/base/mutex-inl.h
+++ b/runtime/base/mutex-inl.h
@@ -80,7 +80,9 @@ static inline void CheckUnattachedThread(LockLevel level) NO_THREAD_SAFETY_ANALY
// (see Thread::TransitionFromSuspendedToRunnable).
level == kThreadSuspendCountLock ||
// Avoid recursive death.
- level == kAbortLock) << level;
+ level == kAbortLock ||
+ // Locks at the absolute top of the stack can be locked at any time.
+ level == kTopLockLevel) << level;
}
}
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 87c4afe96f..43ea3a2053 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -122,6 +122,11 @@ enum LockLevel {
kInstrumentEntrypointsLock,
kZygoteCreationLock,
+ // The highest valid lock level. Use this if there is code that should only be called with no
+ // other locks held. Since this is the highest lock level we also allow it to be held even if the
+ // runtime or current thread is not fully set-up yet (for example during thread attach).
+ kTopLockLevel,
+
kLockLevelCount // Must come last.
};
std::ostream& operator<<(std::ostream& os, const LockLevel& rhs);
diff --git a/runtime/ti/agent.cc b/runtime/ti/agent.cc
index 3bf169ad40..548752e980 100644
--- a/runtime/ti/agent.cc
+++ b/runtime/ti/agent.cc
@@ -21,6 +21,8 @@
#include "base/strlcpy.h"
#include "java_vm_ext.h"
#include "runtime.h"
+#include "thread-current-inl.h"
+#include "scoped_thread_state_change-inl.h"
namespace art {
namespace ti {
@@ -35,6 +37,7 @@ const char* AGENT_ON_UNLOAD_FUNCTION_NAME = "Agent_OnUnload";
Agent::LoadError Agent::DoLoadHelper(bool attaching,
/*out*/jint* call_res,
/*out*/std::string* error_msg) {
+ ScopedThreadStateChange stsc(Thread::Current(), ThreadState::kNative);
DCHECK(call_res != nullptr);
DCHECK(error_msg != nullptr);
diff --git a/test/1941-dispose-stress/dispose_stress.cc b/test/1941-dispose-stress/dispose_stress.cc
new file mode 100644
index 0000000000..e8fcc775e9
--- /dev/null
+++ b/test/1941-dispose-stress/dispose_stress.cc
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#include <atomic>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1941DisposeStress {
+
+extern "C" JNIEXPORT jlong JNICALL Java_art_Test1941_AllocEnv(JNIEnv* env, jclass) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != 0) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), "Unable to get JavaVM");
+ return -1;
+ }
+ jvmtiEnv* new_env = nullptr;
+ if (vm->GetEnv(reinterpret_cast<void**>(&new_env), JVMTI_VERSION_1_0) != 0) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), "Unable to create new jvmtiEnv");
+ return -1;
+ }
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(new_env));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1941_FreeEnv(JNIEnv* env,
+ jclass,
+ jlong jvmti_env_ptr) {
+ JvmtiErrorToException(env,
+ jvmti_env,
+ reinterpret_cast<jvmtiEnv*>(jvmti_env_ptr)->DisposeEnvironment());
+}
+
+} // namespace Test1941DisposeStress
+} // namespace art
+
diff --git a/test/1941-dispose-stress/expected.txt b/test/1941-dispose-stress/expected.txt
new file mode 100644
index 0000000000..ca2eddc7b8
--- /dev/null
+++ b/test/1941-dispose-stress/expected.txt
@@ -0,0 +1 @@
+fib(20) is 6765
diff --git a/test/1941-dispose-stress/info.txt b/test/1941-dispose-stress/info.txt
new file mode 100644
index 0000000000..e4a584e46f
--- /dev/null
+++ b/test/1941-dispose-stress/info.txt
@@ -0,0 +1,3 @@
+Test basic JVMTI single step functionality.
+
+Ensures that we can receive single step events from JVMTI.
diff --git a/test/1941-dispose-stress/run b/test/1941-dispose-stress/run
new file mode 100755
index 0000000000..51875a7e86
--- /dev/null
+++ b/test/1941-dispose-stress/run
@@ -0,0 +1,18 @@
+#!/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.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1941-dispose-stress/src/Main.java b/test/1941-dispose-stress/src/Main.java
new file mode 100644
index 0000000000..2fe6b818a0
--- /dev/null
+++ b/test/1941-dispose-stress/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1941.run();
+ }
+}
diff --git a/test/1941-dispose-stress/src/art/Breakpoint.java b/test/1941-dispose-stress/src/art/Breakpoint.java
new file mode 100644
index 0000000000..bbb89f707f
--- /dev/null
+++ b/test/1941-dispose-stress/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+ public static class Manager {
+ public static class BP {
+ public final Executable method;
+ public final long location;
+
+ public BP(Executable method) {
+ this(method, getStartLocation(method));
+ }
+
+ public BP(Executable method, long location) {
+ this.method = method;
+ this.location = location;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return (other instanceof BP) &&
+ method.equals(((BP)other).method) &&
+ location == ((BP)other).location;
+ }
+
+ @Override
+ public String toString() {
+ return method.toString() + " @ " + getLine();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(method, location);
+ }
+
+ public int getLine() {
+ try {
+ LineNumber[] lines = getLineNumberTable(method);
+ int best = -1;
+ for (LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+ }
+
+ private Set<BP> breaks = new HashSet<>();
+
+ public void setBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.add(b)) {
+ Breakpoint.setBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void setBreakpoint(Executable method, long location) {
+ setBreakpoints(new BP(method, location));
+ }
+
+ public void clearBreakpoints(BP... bs) {
+ for (BP b : bs) {
+ if (breaks.remove(b)) {
+ Breakpoint.clearBreakpoint(b.method, b.location);
+ }
+ }
+ }
+ public void clearBreakpoint(Executable method, long location) {
+ clearBreakpoints(new BP(method, location));
+ }
+
+ public void clearAllBreakpoints() {
+ clearBreakpoints(breaks.toArray(new BP[0]));
+ }
+ }
+
+ public static void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ Thread thr) {
+ startBreakpointWatch(methodClass, breakpointReached, false, thr);
+ }
+
+ /**
+ * Enables the trapping of breakpoint events.
+ *
+ * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+ */
+ public static native void startBreakpointWatch(Class<?> methodClass,
+ Executable breakpointReached,
+ boolean allowRecursive,
+ Thread thr);
+ public static native void stopBreakpointWatch(Thread thr);
+
+ public static final class LineNumber implements Comparable<LineNumber> {
+ public final long location;
+ public final int line;
+
+ private LineNumber(long loc, int line) {
+ this.location = loc;
+ this.line = line;
+ }
+
+ public boolean equals(Object other) {
+ return other instanceof LineNumber && ((LineNumber)other).line == line &&
+ ((LineNumber)other).location == location;
+ }
+
+ public int compareTo(LineNumber other) {
+ int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+ if (v != 0) {
+ return v;
+ } else {
+ return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+ }
+ }
+ }
+
+ public static native void setBreakpoint(Executable m, long loc);
+ public static void setBreakpoint(Executable m, LineNumber l) {
+ setBreakpoint(m, l.location);
+ }
+
+ public static native void clearBreakpoint(Executable m, long loc);
+ public static void clearBreakpoint(Executable m, LineNumber l) {
+ clearBreakpoint(m, l.location);
+ }
+
+ private static native Object[] getLineNumberTableNative(Executable m);
+ public static LineNumber[] getLineNumberTable(Executable m) {
+ Object[] nativeTable = getLineNumberTableNative(m);
+ long[] location = (long[])(nativeTable[0]);
+ int[] lines = (int[])(nativeTable[1]);
+ if (lines.length != location.length) {
+ throw new Error("Lines and locations have different lengths!");
+ }
+ LineNumber[] out = new LineNumber[lines.length];
+ for (int i = 0; i < lines.length; i++) {
+ out[i] = new LineNumber(location[i], lines[i]);
+ }
+ return out;
+ }
+
+ public static native long getStartLocation(Executable m);
+
+ public static int locationToLine(Executable m, long location) {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ int best = -1;
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.location > location) {
+ break;
+ } else {
+ best = l.line;
+ }
+ }
+ return best;
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ public static long lineToLocation(Executable m, int line) throws Exception {
+ try {
+ Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+ for (Breakpoint.LineNumber l : lines) {
+ if (l.line == line) {
+ return l.location;
+ }
+ }
+ throw new Exception("Unable to find line " + line + " in " + m);
+ } catch (Exception e) {
+ throw new Exception("Unable to get line number info for " + m, e);
+ }
+ }
+}
+
diff --git a/test/1941-dispose-stress/src/art/Test1941.java b/test/1941-dispose-stress/src/art/Test1941.java
new file mode 100644
index 0000000000..d5a9de6cab
--- /dev/null
+++ b/test/1941-dispose-stress/src/art/Test1941.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.util.Arrays;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+
+public class Test1941 {
+ public static final boolean PRINT_CNT = false;
+ public static long CNT = 0;
+
+ // Method with multiple paths we can break on.
+ public static long fib(long f) {
+ if (f < 0) {
+ throw new IllegalArgumentException("Bad argument f < 0: f = " + f);
+ } else if (f == 0) {
+ return 0;
+ } else if (f == 1) {
+ return 1;
+ } else {
+ return fib(f - 1) + fib(f - 2);
+ }
+ }
+
+ public static void notifySingleStep(Thread thr, Executable e, long loc) {
+ // Don't bother actually doing anything.
+ }
+
+ public static void LoopAllocFreeEnv() {
+ while (!Thread.interrupted()) {
+ CNT++;
+ long env = AllocEnv();
+ FreeEnv(env);
+ }
+ }
+
+ public static native long AllocEnv();
+ public static native void FreeEnv(long env);
+
+ public static void run() throws Exception {
+ Thread thr = new Thread(Test1941::LoopAllocFreeEnv, "LoopNative");
+ thr.start();
+ Trace.enableSingleStepTracing(Test1941.class,
+ Test1941.class.getDeclaredMethod(
+ "notifySingleStep", Thread.class, Executable.class, Long.TYPE),
+ null);
+
+ System.out.println("fib(20) is " + fib(20));
+
+ thr.interrupt();
+ thr.join();
+ Trace.disableTracing(null);
+ if (PRINT_CNT) {
+ System.out.println("Number of envs created/destroyed: " + CNT);
+ }
+ }
+}
diff --git a/test/1941-dispose-stress/src/art/Trace.java b/test/1941-dispose-stress/src/art/Trace.java
new file mode 100644
index 0000000000..8999bb1368
--- /dev/null
+++ b/test/1941-dispose-stress/src/art/Trace.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class Trace {
+ public static native void enableTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Method fieldAccess,
+ Method fieldModify,
+ Method singleStep,
+ Thread thr);
+ public static native void disableTracing(Thread thr);
+
+ public static void enableFieldTracing(Class<?> methodClass,
+ Method fieldAccess,
+ Method fieldModify,
+ Thread thr) {
+ enableTracing(methodClass, null, null, fieldAccess, fieldModify, null, thr);
+ }
+
+ public static void enableMethodTracing(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Thread thr) {
+ enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
+ }
+
+ public static void enableSingleStepTracing(Class<?> methodClass,
+ Method singleStep,
+ Thread thr) {
+ enableTracing(methodClass, null, null, null, null, singleStep, thr);
+ }
+
+ public static native void watchFieldAccess(Field f);
+ public static native void watchFieldModification(Field f);
+ public static native void watchAllFieldAccesses();
+ public static native void watchAllFieldModifications();
+
+ // the names, arguments, and even line numbers of these functions are embedded in the tests so we
+ // need to add to the bottom and not modify old ones to maintain compat.
+ public static native void enableTracing2(Class<?> methodClass,
+ Method entryMethod,
+ Method exitMethod,
+ Method fieldAccess,
+ Method fieldModify,
+ Method singleStep,
+ Method ThreadStart,
+ Method ThreadEnd,
+ Thread thr);
+}
diff --git a/test/Android.bp b/test/Android.bp
index ba24119e9c..8f29251907 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -259,6 +259,7 @@ art_cc_defaults {
"1932-monitor-events-misc/monitor_misc.cc",
"1934-jvmti-signal-thread/signal_threads.cc",
"1939-proxy-frames/local_instance.cc",
+ "1941-dispose-stress/dispose_stress.cc",
],
shared_libs: [
"libbase",