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