JVMTI NotifyFramePop support

Adds support for the JVMTI can_generate_frame_pop_events capability.
This includes the NotifyFramePop function and the FramePop event.

We mark the interpreter shadowframes directly to get the events. This
relies on the fact that we never replace extant shadow-frames on the
interpreter stack to ensure that we can distinguish which jvmti-envs
requested the frame pops.

Test: ./test.py --host -j50
Bug: 34414072
Bug: 62821960
Bug: 65129403

Change-Id: I6e79e39f62fdf79268540c5c1be6311df704cff7
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 1cca36b..6c0d492 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -318,12 +318,10 @@
     return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr);
   }
 
-  static jvmtiError NotifyFramePop(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED) {
+  static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_generate_frame_pop_events);
-    return ERR(NOT_IMPLEMENTED);
+    return StackUtil::NotifyFramePop(env, thread, depth);
   }
 
   static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env,
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 71a8d30..d74e25b 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -52,6 +52,7 @@
 namespace art {
 class ArtField;
 class ArtMethod;
+class ShadowFrame;
 }  // namespace art
 
 namespace openjdkjvmti {
@@ -81,6 +82,7 @@
 
   // Set of breakpoints is unique to each jvmtiEnv.
   std::unordered_set<Breakpoint> breakpoints;
+  std::unordered_set<const art::ShadowFrame*> notify_frames;
 
   ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);
 
@@ -235,7 +237,7 @@
     .can_maintain_original_method_order              = 1,
     .can_generate_single_step_events                 = 1,
     .can_generate_exception_events                   = 0,
-    .can_generate_frame_pop_events                   = 0,
+    .can_generate_frame_pop_events                   = 1,
     .can_generate_breakpoint_events                  = 1,
     .can_suspend                                     = 1,
     .can_redefine_any_class                          = 0,
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index 32dba3e..31edc73 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -244,6 +244,35 @@
   }
 }
 
+// Need to give custom specializations for FramePop since it needs to filter out which particular
+// agents get the event. This specialization gets an extra argument so we can determine which (if
+// any) environments have the frame pop.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFramePop>(
+    art::Thread* thread,
+    JNIEnv* jnienv,
+    jthread jni_thread,
+    jmethodID jmethod,
+    jboolean is_exception,
+    const art::ShadowFrame* frame) const {
+  for (ArtJvmTiEnv* env : envs) {
+    // Search for the frame. Do this before checking if we need to send the event so that we don't
+    // have to deal with use-after-free or the frames being reallocated later.
+    if (env != nullptr && env->notify_frames.erase(frame) != 0) {
+      if (ShouldDispatch<ArtJvmtiEvent::kFramePop>(env, thread)) {
+        // We temporarily clear any pending exceptions so the event can call back into java code.
+        ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+        jnienv->ExceptionClear();
+        auto callback = impl::GetCallback<ArtJvmtiEvent::kFramePop>(env);
+        (*callback)(env, jnienv, jni_thread, jmethod, is_exception);
+        if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+          jnienv->Throw(thr.get());
+        }
+      }
+    }
+  }
+}
+
 // Need to give custom specializations for FieldAccess and FieldModification since they need to
 // filter out which particular fields agents want to get notified on.
 // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 73e6881..acef682 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -538,6 +538,20 @@
     }
   }
 
+  void WatchedFramePop(art::Thread* self, const art::ShadowFrame& frame)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFramePop)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      jboolean is_exception_pending = self->IsExceptionPending();
+      RunEventCallback<ArtJvmtiEvent::kFramePop>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(frame.GetMethod()),
+          is_exception_pending,
+          &frame);
+    }
+  }
+
   // Call-back when an exception is thrown.
   void ExceptionThrown(art::Thread* self ATTRIBUTE_UNUSED,
                        art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED)
@@ -582,6 +596,8 @@
     case ArtJvmtiEvent::kBreakpoint:
     case ArtJvmtiEvent::kSingleStep:
       return art::instrumentation::Instrumentation::kDexPcMoved;
+    case ArtJvmtiEvent::kFramePop:
+      return art::instrumentation::Instrumentation::kWatchedFramePop;
     default:
       LOG(FATAL) << "Unknown event ";
       return 0;
@@ -648,6 +664,15 @@
       }
       return;
     }
+    // FramePop can never be disabled once it's been turned on since we would either need to deal
+    // with dangling pointers or have missed events.
+    case ArtJvmtiEvent::kFramePop:
+      if (!enable || (enable && frame_pop_enabled)) {
+        break;
+      } else {
+        SetupTraceListener(method_trace_listener_.get(), event, enable);
+        break;
+      }
     case ArtJvmtiEvent::kMethodEntry:
     case ArtJvmtiEvent::kMethodExit:
     case ArtJvmtiEvent::kFieldAccess:
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index 3d05fa1..b49e80c 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -223,6 +223,11 @@
   std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
   std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
   std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+
+  // True if frame pop has ever been enabled. Since we store pointers to stack frames we need to
+  // 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;
 };
 
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
index 35f2f0c..f99b167 100644
--- a/openjdkjvmti/ti_method.cc
+++ b/openjdkjvmti/ti_method.cc
@@ -51,6 +51,7 @@
 #include "stack.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
+#include "ti_stack.h"
 #include "ti_thread.h"
 #include "ti_phase.h"
 
@@ -533,39 +534,6 @@
   return IsMethodT(env, m, test, is_synthetic_ptr);
 }
 
-struct FindFrameAtDepthVisitor : art::StackVisitor {
- public:
-  FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
-      REQUIRES_SHARED(art::Locks::mutator_lock_)
-      : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
-        found_frame_(false),
-        cnt_(0),
-        depth_(static_cast<size_t>(depth)) { }
-
-  bool FoundFrame() {
-    return found_frame_;
-  }
-
-  bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
-    if (GetMethod()->IsRuntimeMethod()) {
-      return true;
-    }
-    if (cnt_ == depth_) {
-      // We found our frame, exit.
-      found_frame_ = true;
-      return false;
-    } else {
-      cnt_++;
-      return true;
-    }
-  }
-
- private:
-  bool found_frame_;
-  size_t cnt_;
-  size_t depth_;
-};
-
 class CommonLocalVariableClosure : public art::Closure {
  public:
   CommonLocalVariableClosure(art::Thread* caller,
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index 20de9aa..e165187 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -37,6 +37,7 @@
 #include <vector>
 
 #include "art_field-inl.h"
+#include "art_method-inl.h"
 #include "art_jvmti.h"
 #include "art_method-inl.h"
 #include "barrier.h"
@@ -54,6 +55,7 @@
 #include "nativehelper/ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
+#include "ti_thread.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
 #include "thread_pool.h"
@@ -991,4 +993,74 @@
   return GetOwnedMonitorInfoCommon(thread, handle_fun);
 }
 
+jvmtiError StackUtil::NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+  art::Thread* self = art::Thread::Current();
+  art::Thread* target;
+  do {
+    ThreadUtil::SuspendCheck(self);
+    art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+    // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by a
+    // user-code suspension. We retry and do another SuspendCheck to clear this.
+    if (ThreadUtil::WouldSuspendForUserCodeLocked(self)) {
+      continue;
+    }
+    // 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::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+    target = ThreadUtil::GetNativeThread(thread, soa);
+    if (target == nullptr) {
+      return ERR(THREAD_NOT_ALIVE);
+    } else if (target != self) {
+      // TODO This is part of the spec but we could easily avoid needing to do it. We would just put
+      // all the logic into a sync-checkpoint.
+      art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+      if (target->GetUserCodeSuspendCount() == 0) {
+        return ERR(THREAD_NOT_SUSPENDED);
+      }
+    }
+    // We hold the user_code_suspension_lock_ so the target thread is staying suspended until we are
+    // done (unless it's 'self' in which case we don't care since we aren't going to be returning).
+    // TODO We could implement this using a synchronous checkpoint and not bother with any of the
+    // suspension stuff. The spec does specifically say to return THREAD_NOT_SUSPENDED though.
+    // Find the requested stack frame.
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(target, context.get(), depth);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      return ERR(NO_MORE_FRAMES);
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (method->IsNative()) {
+      return ERR(OPAQUE_FRAME);
+    }
+    // From here we are sure to succeed.
+    bool needs_instrument = false;
+    // Get/create a shadow frame
+    art::ShadowFrame* shadow_frame = visitor.GetCurrentShadowFrame();
+    if (shadow_frame == nullptr) {
+      needs_instrument = true;
+      const size_t frame_id = visitor.GetFrameId();
+      const uint16_t num_regs = method->GetCodeItem()->registers_size_;
+      shadow_frame = target->FindOrCreateDebuggerShadowFrame(frame_id,
+                                                             num_regs,
+                                                             method,
+                                                             visitor.GetDexPc());
+    }
+    // Mark shadow frame as needs_notify_pop_
+    shadow_frame->SetNotifyPop(true);
+    tienv->notify_frames.insert(shadow_frame);
+    // Make sure can we will go to the interpreter and use the shadow frames.
+    if (needs_instrument) {
+      art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(target);
+    }
+    return OK;
+  } while (true);
+}
+
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
index 2f506d0..b41fa4b 100644
--- a/openjdkjvmti/ti_stack.h
+++ b/openjdkjvmti/ti_stack.h
@@ -35,7 +35,9 @@
 #include "jni.h"
 #include "jvmti.h"
 
+#include "art_method.h"
 #include "base/mutex.h"
+#include "stack.h"
 
 namespace openjdkjvmti {
 
@@ -77,6 +79,41 @@
                                         jthread thread,
                                         jint* owned_monitor_count_ptr,
                                         jobject** owned_monitors_ptr);
+
+  static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth);
+};
+
+struct FindFrameAtDepthVisitor : art::StackVisitor {
+ public:
+  FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        found_frame_(false),
+        cnt_(0),
+        depth_(static_cast<size_t>(depth)) { }
+
+  bool FoundFrame() {
+    return found_frame_;
+  }
+
+  bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+    if (GetMethod()->IsRuntimeMethod()) {
+      return true;
+    }
+    if (cnt_ == depth_) {
+      // We found our frame, exit.
+      found_frame_ = true;
+      return false;
+    } else {
+      cnt_++;
+      return true;
+    }
+  }
+
+ private:
+  bool found_frame_;
+  size_t cnt_;
+  size_t depth_;
 };
 
 }  // namespace openjdkjvmti