ART: Add CheckJNI lock checking
JNI MonitorEnter and MonitorExit have similar rules to structured
locking. Count locks in CheckJNI mode.
Bug: 23502994
Change-Id: Ie3f53d3aa669a6bd0c7153c50c168116b43764d9
diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc
index b18b430..4104d7a 100644
--- a/runtime/jni_env_ext.cc
+++ b/runtime/jni_env_ext.cc
@@ -16,10 +16,17 @@
#include "jni_env_ext.h"
+#include <algorithm>
+#include <vector>
+
#include "check_jni.h"
#include "indirect_reference_table.h"
#include "java_vm_ext.h"
#include "jni_internal.h"
+#include "lock_word.h"
+#include "mirror/object-inl.h"
+#include "nth_caller_visitor.h"
+#include "thread-inl.h"
namespace art {
@@ -63,14 +70,14 @@
JNIEnvExt::~JNIEnvExt() {
}
-jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_) {
+jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) {
if (obj == nullptr) {
return nullptr;
}
return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj));
}
-void JNIEnvExt::DeleteLocalRef(jobject obj) SHARED_REQUIRES(Locks::mutator_lock_) {
+void JNIEnvExt::DeleteLocalRef(jobject obj) {
if (obj != nullptr) {
locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj));
}
@@ -86,14 +93,14 @@
monitors.Dump(os);
}
-void JNIEnvExt::PushFrame(int capacity) SHARED_REQUIRES(Locks::mutator_lock_) {
+void JNIEnvExt::PushFrame(int capacity) {
UNUSED(capacity); // cpplint gets confused with (int) and thinks its a cast.
// TODO: take 'capacity' into account.
stacked_local_ref_cookies.push_back(local_ref_cookie);
local_ref_cookie = locals.GetSegmentState();
}
-void JNIEnvExt::PopFrame() SHARED_REQUIRES(Locks::mutator_lock_) {
+void JNIEnvExt::PopFrame() {
locals.SetSegmentState(local_ref_cookie);
local_ref_cookie = stacked_local_ref_cookies.back();
stacked_local_ref_cookies.pop_back();
@@ -104,4 +111,118 @@
IndirectReferenceTable::SegmentStateOffset().Int32Value());
}
+// Use some defining part of the caller's frame as the identifying mark for the JNI segment.
+static uintptr_t GetJavaCallFrame(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) {
+ NthCallerVisitor zeroth_caller(self, 0, false);
+ zeroth_caller.WalkStack();
+ if (zeroth_caller.caller == nullptr) {
+ // No Java code, must be from pure native code.
+ return 0;
+ } else if (zeroth_caller.GetCurrentQuickFrame() == nullptr) {
+ // Shadow frame = interpreter. Use the actual shadow frame's address.
+ DCHECK(zeroth_caller.GetCurrentShadowFrame() != nullptr);
+ return reinterpret_cast<uintptr_t>(zeroth_caller.GetCurrentShadowFrame());
+ } else {
+ // Quick frame = compiled code. Use the bottom of the frame.
+ return reinterpret_cast<uintptr_t>(zeroth_caller.GetCurrentQuickFrame());
+ }
+}
+
+void JNIEnvExt::RecordMonitorEnter(jobject obj) {
+ locked_objects_.push_back(std::make_pair(GetJavaCallFrame(self), obj));
+}
+
+static std::string ComputeMonitorDescription(Thread* self,
+ jobject obj) SHARED_REQUIRES(Locks::mutator_lock_) {
+ mirror::Object* o = self->DecodeJObject(obj);
+ if ((o->GetLockWord(false).GetState() == LockWord::kThinLocked) &&
+ Locks::mutator_lock_->IsExclusiveHeld(self)) {
+ // Getting the identity hashcode here would result in lock inflation and suspension of the
+ // current thread, which isn't safe if this is the only runnable thread.
+ return StringPrintf("<@addr=0x%" PRIxPTR "> (a %s)",
+ reinterpret_cast<intptr_t>(o),
+ PrettyTypeOf(o).c_str());
+ } else {
+ // IdentityHashCode can cause thread suspension, which would invalidate o if it moved. So
+ // we get the pretty type before we call IdentityHashCode.
+ const std::string pretty_type(PrettyTypeOf(o));
+ return StringPrintf("<0x%08x> (a %s)", o->IdentityHashCode(), pretty_type.c_str());
+ }
+}
+
+static void RemoveMonitors(Thread* self,
+ uintptr_t frame,
+ ReferenceTable* monitors,
+ std::vector<std::pair<uintptr_t, jobject>>* locked_objects)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ auto kept_end = std::remove_if(
+ locked_objects->begin(),
+ locked_objects->end(),
+ [self, frame, monitors](const std::pair<uintptr_t, jobject>& pair)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (frame == pair.first) {
+ mirror::Object* o = self->DecodeJObject(pair.second);
+ monitors->Remove(o);
+ return true;
+ }
+ return false;
+ });
+ locked_objects->erase(kept_end, locked_objects->end());
+}
+
+void JNIEnvExt::CheckMonitorRelease(jobject obj) {
+ uintptr_t current_frame = GetJavaCallFrame(self);
+ std::pair<uintptr_t, jobject> exact_pair = std::make_pair(current_frame, obj);
+ auto it = std::find(locked_objects_.begin(), locked_objects_.end(), exact_pair);
+ bool will_abort = false;
+ if (it != locked_objects_.end()) {
+ locked_objects_.erase(it);
+ } else {
+ // Check whether this monitor was locked in another JNI "session."
+ mirror::Object* mirror_obj = self->DecodeJObject(obj);
+ for (std::pair<uintptr_t, jobject>& pair : locked_objects_) {
+ if (self->DecodeJObject(pair.second) == mirror_obj) {
+ std::string monitor_descr = ComputeMonitorDescription(self, pair.second);
+ vm->JniAbortF("<JNI MonitorExit>",
+ "Unlocking monitor that wasn't locked here: %s",
+ monitor_descr.c_str());
+ will_abort = true;
+ break;
+ }
+ }
+ }
+
+ // When we abort, also make sure that any locks from the current "session" are removed from
+ // the monitors table, otherwise we may visit local objects in GC during abort (which won't be
+ // valid anymore).
+ if (will_abort) {
+ RemoveMonitors(self, current_frame, &monitors, &locked_objects_);
+ }
+}
+
+void JNIEnvExt::CheckNoHeldMonitors() {
+ uintptr_t current_frame = GetJavaCallFrame(self);
+ // The locked_objects_ are grouped by their stack frame component, as this enforces structured
+ // locking, and the groups form a stack. So the current frame entries are at the end. Check
+ // whether the vector is empty, and when there are elements, whether the last element belongs
+ // to this call - this signals that there are unlocked monitors.
+ if (!locked_objects_.empty()) {
+ std::pair<uintptr_t, jobject>& pair = locked_objects_[locked_objects_.size() - 1];
+ if (pair.first == current_frame) {
+ std::string monitor_descr = ComputeMonitorDescription(self, pair.second);
+ vm->JniAbortF("<JNI End>",
+ "Still holding a locked object on JNI end: %s",
+ monitor_descr.c_str());
+ // When we abort, also make sure that any locks from the current "session" are removed from
+ // the monitors table, otherwise we may visit local objects in GC during abort.
+ RemoveMonitors(self, current_frame, &monitors, &locked_objects_);
+ } else if (kIsDebugBuild) {
+ // Make sure there are really no other entries and our checking worked as expected.
+ for (std::pair<uintptr_t, jobject>& check_pair : locked_objects_) {
+ CHECK_NE(check_pair.first, current_frame);
+ }
+ }
+ }
+}
+
} // namespace art