Merge "Revert^4 "JVMTI PopFrame support""
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 3213bbe..48f326a 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -313,10 +313,10 @@
     return StackUtil::GetFrameCount(env, thread, count_ptr);
   }
 
-  static jvmtiError PopFrame(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+  static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_pop_frame);
-    return ERR(NOT_IMPLEMENTED);
+    return StackUtil::PopFrame(env, thread);
   }
 
   static jvmtiError GetFrameLocation(jvmtiEnv* env,
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 82f3866..1218e3b 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -249,7 +249,7 @@
     .can_get_owned_monitor_info                      = 1,
     .can_get_current_contended_monitor               = 1,
     .can_get_monitor_info                            = 1,
-    .can_pop_frame                                   = 0,
+    .can_pop_frame                                   = 1,
     .can_redefine_classes                            = 1,
     .can_signal_thread                               = 1,
     .can_get_source_file_name                        = 1,
@@ -291,6 +291,7 @@
 //   can_retransform_classes:
 //   can_redefine_any_class:
 //   can_redefine_classes:
+//   can_pop_frame:
 //     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.
@@ -303,7 +304,7 @@
     .can_get_owned_monitor_info                      = 0,
     .can_get_current_contended_monitor               = 0,
     .can_get_monitor_info                            = 0,
-    .can_pop_frame                                   = 0,
+    .can_pop_frame                                   = 1,
     .can_redefine_classes                            = 1,
     .can_signal_thread                               = 0,
     .can_get_source_file_name                        = 0,
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index e98517f..ca66556 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -26,7 +26,9 @@
 #include "jni/jni_internal.h"
 #include "nativehelper/scoped_local_ref.h"
 #include "scoped_thread_state_change-inl.h"
+#include "stack.h"
 #include "ti_breakpoint.h"
+#include "ti_thread.h"
 
 #include "art_jvmti.h"
 
@@ -359,6 +361,7 @@
   // 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() &&
       ShouldDispatchOnThread<ArtJvmtiEvent::kFramePop>(env, thread);
 }
 
@@ -418,6 +421,67 @@
   ExecuteCallback<ArtJvmtiEvent::kFramePop>(event, jnienv, jni_thread, jmethod, is_exception);
 }
 
+struct ScopedDisablePopFrame {
+ public:
+  explicit ScopedDisablePopFrame(art::Thread* thread) : thread_(thread) {
+    art::Locks::mutator_lock_->AssertSharedHeld(thread_);
+    art::MutexLock mu(thread_, *art::Locks::thread_list_lock_);
+    JvmtiGlobalTLSData* data = ThreadUtil::GetOrCreateGlobalTLSData(thread_);
+    current_top_frame_ = art::StackVisitor::ComputeNumFrames(
+        thread_, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
+    old_disable_frame_pop_depth_ = data->disable_pop_frame_depth;
+    data->disable_pop_frame_depth = current_top_frame_;
+    DCHECK(old_disable_frame_pop_depth_ == JvmtiGlobalTLSData::kNoDisallowedPopFrame ||
+           current_top_frame_ > old_disable_frame_pop_depth_)
+        << "old: " << old_disable_frame_pop_depth_ << " current: " << current_top_frame_;
+  }
+
+  ~ScopedDisablePopFrame() {
+    art::Locks::mutator_lock_->AssertSharedHeld(thread_);
+    art::MutexLock mu(thread_, *art::Locks::thread_list_lock_);
+    JvmtiGlobalTLSData* data = ThreadUtil::GetGlobalTLSData(thread_);
+    DCHECK_EQ(data->disable_pop_frame_depth, current_top_frame_);
+    data->disable_pop_frame_depth = old_disable_frame_pop_depth_;
+  }
+
+ private:
+  art::Thread* thread_;
+  size_t current_top_frame_;
+  size_t old_disable_frame_pop_depth_;
+};
+// We want to prevent the use of PopFrame when reporting either of these events.
+template <ArtJvmtiEvent kEvent>
+inline void EventHandler::DispatchClassLoadOrPrepareEvent(art::Thread* thread,
+                                                          JNIEnv* jnienv,
+                                                          jthread jni_thread,
+                                                          jclass klass) const {
+  ScopedDisablePopFrame sdpf(thread);
+  art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative);
+  std::vector<impl::EventHandlerFunc<kEvent>> events = CollectEvents<kEvent>(thread,
+                                                                             jnienv,
+                                                                             jni_thread,
+                                                                             klass);
+
+  for (auto event : events) {
+    ExecuteCallback<kEvent>(event, jnienv, jni_thread, klass);
+  }
+}
+
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassLoad>(art::Thread* thread,
+                                                                   JNIEnv* jnienv,
+                                                                   jthread jni_thread,
+                                                                   jclass klass) const {
+  DispatchClassLoadOrPrepareEvent<ArtJvmtiEvent::kClassLoad>(thread, jnienv, jni_thread, klass);
+}
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassPrepare>(art::Thread* thread,
+                                                                      JNIEnv* jnienv,
+                                                                      jthread jni_thread,
+                                                                      jclass klass) const {
+  DispatchClassLoadOrPrepareEvent<ArtJvmtiEvent::kClassPrepare>(thread, jnienv, jni_thread, klass);
+}
+
 // Need to give a custom specialization for NativeMethodBind since it has to deal with an out
 // variable.
 template <>
@@ -553,6 +617,7 @@
                               : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
   return (added && caps.can_access_local_variables == 1) ||
       caps.can_generate_breakpoint_events == 1 ||
+      caps.can_pop_frame == 1 ||
       (caps.can_retransform_classes == 1 &&
        IsEventEnabledAnywhere(event) &&
        env->event_masks.IsEnabledAnywhere(event));
@@ -573,6 +638,11 @@
     if (caps.can_generate_breakpoint_events == 1) {
       HandleBreakpointEventsChanged(added);
     }
+    if (caps.can_pop_frame == 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.h b/openjdkjvmti/events.h
index bf12cb1..9f91a08 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -301,6 +301,13 @@
                                                            unsigned char** new_class_data) const
       REQUIRES(!envs_lock_);
 
+  template <ArtJvmtiEvent kEvent>
+  ALWAYS_INLINE inline void DispatchClassLoadOrPrepareEvent(art::Thread* thread,
+                                                            JNIEnv* jnienv,
+                                                            jthread jni_thread,
+                                                            jclass klass) const
+      REQUIRES(!envs_lock_);
+
   void HandleEventType(ArtJvmtiEvent event, bool enable);
   void HandleLocalAccessCapabilityAdded();
   void HandleBreakpointEventsChanged(bool enable);
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index 220ad22..5a98755 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -112,6 +112,23 @@
   size_t stop;
 };
 
+art::ShadowFrame* FindFrameAtDepthVisitor::GetOrCreateShadowFrame(bool* created_frame) {
+  art::ShadowFrame* cur = GetCurrentShadowFrame();
+  if (cur == nullptr) {
+    *created_frame = true;
+    art::ArtMethod* method = GetMethod();
+    const uint16_t num_regs = method->DexInstructionData().RegistersSize();
+    cur = GetThread()->FindOrCreateDebuggerShadowFrame(GetFrameId(),
+                                                       num_regs,
+                                                       method,
+                                                       GetDexPc());
+    DCHECK(cur != nullptr);
+  } else {
+    *created_frame = false;
+  }
+  return cur;
+}
+
 template <typename FrameFn>
 GetStackTraceVisitor<FrameFn> MakeStackTraceVisitor(art::Thread* thread_in,
                                                     size_t start,
@@ -1065,16 +1082,7 @@
     // 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->DexInstructionData().RegistersSize();
-      shadow_frame = target->FindOrCreateDebuggerShadowFrame(frame_id,
-                                                             num_regs,
-                                                             method,
-                                                             visitor.GetDexPc());
-    }
+    art::ShadowFrame* shadow_frame = visitor.GetOrCreateShadowFrame(&needs_instrument);
     {
       art::WriterMutexLock lk(self, tienv->event_info_mutex_);
       // Mark shadow frame as needs_notify_pop_
@@ -1089,4 +1097,88 @@
   } while (true);
 }
 
+jvmtiError StackUtil::PopFrame(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
+  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_);
+    jvmtiError err = ERR(INTERNAL);
+    if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) {
+      return err;
+    }
+    {
+      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.
+        return ERR(THREAD_NOT_SUSPENDED);
+      }
+    }
+    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)) {
+      LOG(WARNING) << "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.";
+      return ERR(OPAQUE_FRAME);
+    }
+    // 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!
+      return ERR(NO_MORE_FRAMES);
+    }
+
+    art::ArtMethod* called_method = final_frame.GetMethod();
+    art::ArtMethod* calling_method = penultimate_frame.GetMethod();
+    if (calling_method->IsNative() || called_method->IsNative()) {
+      return ERR(OPAQUE_FRAME);
+    }
+    // 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);
+
+    CHECK_NE(called_shadow_frame, calling_shadow_frame)
+        << "Frames at different depths not different!";
+
+    // Tell the shadow-frame to return immediately and skip all exit events.
+    called_shadow_frame->SetForcePopFrame(true);
+    calling_shadow_frame->SetForceRetryInstruction(true);
+
+    // 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) {
+      DeoptManager::Get()->DeoptimizeThread(target);
+    }
+    return OK;
+  } while (true);
+}
+
 }  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
index b41fa4b..55c4269 100644
--- a/openjdkjvmti/ti_stack.h
+++ b/openjdkjvmti/ti_stack.h
@@ -81,6 +81,8 @@
                                         jobject** owned_monitors_ptr);
 
   static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth);
+
+  static jvmtiError PopFrame(jvmtiEnv* env, jthread thread);
 };
 
 struct FindFrameAtDepthVisitor : art::StackVisitor {
@@ -110,6 +112,9 @@
     }
   }
 
+  art::ShadowFrame* GetOrCreateShadowFrame(/*out*/bool* created_frame)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
  private:
   bool found_frame_;
   size_t cnt_;
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index b54c77d..a0e5b5c92 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -623,18 +623,10 @@
   return ERR(NONE);
 }
 
-// The struct that we store in the art::Thread::custom_tls_ that maps the jvmtiEnvs to the data
-// stored with that thread. This is needed since different jvmtiEnvs are not supposed to share TLS
-// data but we only have a single slot in Thread objects to store data.
-struct JvmtiGlobalTLSData : public art::TLSData {
-  std::unordered_map<jvmtiEnv*, const void*> data GUARDED_BY(art::Locks::thread_list_lock_);
-};
-
 static void RemoveTLSData(art::Thread* target, void* ctx) REQUIRES(art::Locks::thread_list_lock_) {
   jvmtiEnv* env = reinterpret_cast<jvmtiEnv*>(ctx);
   art::Locks::thread_list_lock_->AssertHeld(art::Thread::Current());
-  JvmtiGlobalTLSData* global_tls =
-      reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey));
+  JvmtiGlobalTLSData* global_tls = ThreadUtil::GetGlobalTLSData(target);
   if (global_tls != nullptr) {
     global_tls->data.erase(env);
   }
@@ -657,19 +649,27 @@
     return err;
   }
 
-  JvmtiGlobalTLSData* global_tls =
-      reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey));
-  if (global_tls == nullptr) {
-    // Synchronized using thread_list_lock_ to prevent racing sets.
-    target->SetCustomTLS(kJvmtiTlsKey, new JvmtiGlobalTLSData);
-    global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey));
-  }
+  JvmtiGlobalTLSData* global_tls = GetOrCreateGlobalTLSData(target);
 
   global_tls->data[env] = data;
 
   return ERR(NONE);
 }
 
+JvmtiGlobalTLSData* ThreadUtil::GetOrCreateGlobalTLSData(art::Thread* thread) {
+  JvmtiGlobalTLSData* data = GetGlobalTLSData(thread);
+  if (data != nullptr) {
+    return data;
+  } else {
+    thread->SetCustomTLS(kJvmtiTlsKey, new JvmtiGlobalTLSData);
+    return GetGlobalTLSData(thread);
+  }
+}
+
+JvmtiGlobalTLSData* ThreadUtil::GetGlobalTLSData(art::Thread* thread) {
+  return reinterpret_cast<JvmtiGlobalTLSData*>(thread->GetCustomTLS(kJvmtiTlsKey));
+}
+
 jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env,
                                              jthread thread,
                                              void** data_ptr) {
@@ -686,8 +686,7 @@
     return err;
   }
 
-  JvmtiGlobalTLSData* global_tls =
-      reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS(kJvmtiTlsKey));
+  JvmtiGlobalTLSData* global_tls = GetGlobalTLSData(target);
   if (global_tls == nullptr) {
     *data_ptr = nullptr;
     return OK;
diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h
index c6b6af1..39f1f07 100644
--- a/openjdkjvmti/ti_thread.h
+++ b/openjdkjvmti/ti_thread.h
@@ -32,11 +32,14 @@
 #ifndef ART_OPENJDKJVMTI_TI_THREAD_H_
 #define ART_OPENJDKJVMTI_TI_THREAD_H_
 
+#include <unordered_map>
+
 #include "jni.h"
 #include "jvmti.h"
 
 #include "base/macros.h"
 #include "base/mutex.h"
+#include "thread.h"
 
 namespace art {
 class ArtField;
@@ -49,6 +52,18 @@
 
 class EventHandler;
 
+// The struct that we store in the art::Thread::custom_tls_ that maps the jvmtiEnvs to the data
+// stored with that thread. This is needed since different jvmtiEnvs are not supposed to share TLS
+// data but we only have a single slot in Thread objects to store data.
+struct JvmtiGlobalTLSData : public art::TLSData {
+  std::unordered_map<jvmtiEnv*, const void*> data GUARDED_BY(art::Locks::thread_list_lock_);
+
+  // The depth of the last frame where popping using PopFrame it is not allowed. It is set to
+  // kNoDisallowedPopFrame if all frames can be popped. See b/117615146 for more information.
+  static constexpr size_t kNoDisallowedPopFrame = -1;
+  size_t disable_pop_frame_depth = kNoDisallowedPopFrame;
+};
+
 class ThreadUtil {
  public:
   static void Register(EventHandler* event_handler);
@@ -134,6 +149,11 @@
     REQUIRES(!art::Locks::user_code_suspension_lock_,
              !art::Locks::thread_suspend_count_lock_);
 
+  static JvmtiGlobalTLSData* GetGlobalTLSData(art::Thread* thread)
+      REQUIRES(art::Locks::thread_list_lock_);
+  static JvmtiGlobalTLSData* GetOrCreateGlobalTLSData(art::Thread* thread)
+      REQUIRES(art::Locks::thread_list_lock_);
+
  private:
   // We need to make sure only one thread tries to suspend threads at a time so we can get the
   // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a
diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h
index c29043e..15ab5f0 100644
--- a/runtime/common_dex_operations.h
+++ b/runtime/common_dex_operations.h
@@ -27,6 +27,7 @@
 #include "dex/primitive.h"
 #include "handle_scope-inl.h"
 #include "instrumentation.h"
+#include "interpreter/interpreter.h"
 #include "interpreter/shadow_frame.h"
 #include "interpreter/unstarted_runtime.h"
 #include "jvalue-inl.h"
@@ -172,6 +173,14 @@
     if (UNLIKELY(self->IsExceptionPending())) {
       return false;
     }
+    if (shadow_frame.GetForcePopFrame()) {
+      // We need to check this here since we expect that the FieldWriteEvent happens before the
+      // 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;
+    }
   }
 
   switch (field_type) {
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index fccfce4..84631c3 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -753,6 +753,7 @@
   const char* shorty = non_proxy_method->GetShorty(&shorty_len);
 
   JValue result;
+  bool force_frame_pop = false;
 
   if (UNLIKELY(deopt_frame != nullptr)) {
     HandleDeoptimization(&result, method, deopt_frame, &fragment);
@@ -788,6 +789,7 @@
     }
 
     result = interpreter::EnterInterpreterFromEntryPoint(self, accessor, shadow_frame);
+    force_frame_pop = shadow_frame->GetForcePopFrame();
   }
 
   // Pop transition.
@@ -804,12 +806,20 @@
       LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
                    << caller->PrettyMethod();
     } else {
+      VLOG(deopt) << "Forcing deoptimization on return from method " << method->PrettyMethod()
+                  << " to " << caller->PrettyMethod()
+                  << (force_frame_pop ? " for frame-pop" : "");
+      DCHECK(!force_frame_pop || result.GetJ() == 0) << "Force frame pop should have no result.";
+      if (force_frame_pop && self->GetException() != nullptr) {
+        LOG(WARNING) << "Suppressing exception for instruction-retry: "
+                     << self->GetException()->Dump();
+      }
       // Push the context of the deoptimization stack so we can restore the return value and the
       // exception before executing the deoptimized frames.
       self->PushDeoptimizationContext(
           result,
           shorty[0] == 'L' || shorty[0] == '[',  /* class or array */
-          self->GetException(),
+          force_frame_pop ? nullptr : self->GetException(),
           false /* from_code */,
           DeoptimizationMethodType::kDefault);
 
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index df66061..2ae95dc 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -261,6 +261,12 @@
                                         shadow_frame.GetThisObject(accessor.InsSize()),
                                         method,
                                         0);
+      if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
+        // The caller will retry this invoke. Just return immediately without any value.
+        DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+        DCHECK(PrevFrameWillRetry(self, shadow_frame));
+        return JValue();
+      }
       if (UNLIKELY(self->IsExceptionPending())) {
         instrumentation->MethodUnwindEvent(self,
                                            shadow_frame.GetThisObject(accessor.InsSize()),
@@ -494,8 +500,8 @@
   JValue value;
   // Set value to last known result in case the shadow frame chain is empty.
   value.SetJ(ret_val->GetJ());
-  // Are we executing the first shadow frame?
-  bool first = true;
+  // How many frames we have executed.
+  size_t frame_cnt = 0;
   while (shadow_frame != nullptr) {
     // We do not want to recover lock state for lock counting when deoptimizing. Currently,
     // the compiler should not have compiled a method that failed structured-locking checks.
@@ -510,24 +516,30 @@
       // the instrumentation. To prevent from reporting it a second time, we simply pass a
       // null Instrumentation*.
       const instrumentation::Instrumentation* const instrumentation =
-          first ? nullptr : Runtime::Current()->GetInstrumentation();
+          frame_cnt == 0 ? nullptr : Runtime::Current()->GetInstrumentation();
       new_dex_pc = MoveToExceptionHandler(
           self, *shadow_frame, instrumentation) ? shadow_frame->GetDexPC() : dex::kDexNoIndex;
     } else if (!from_code) {
       // Deoptimization is not called from code directly.
       const Instruction* instr = &accessor.InstructionAt(dex_pc);
-      if (deopt_method_type == DeoptimizationMethodType::kKeepDexPc) {
-        DCHECK(first);
+      if (deopt_method_type == DeoptimizationMethodType::kKeepDexPc ||
+          shadow_frame->GetForceRetryInstruction()) {
+        DCHECK(frame_cnt == 0 || (frame_cnt == 1 && shadow_frame->GetForceRetryInstruction()))
+            << "frame_cnt: " << frame_cnt
+            << " force-retry: " << shadow_frame->GetForceRetryInstruction();
         // Need to re-execute the dex instruction.
         // (1) An invocation might be split into class initialization and invoke.
         //     In this case, the invoke should not be skipped.
         // (2) A suspend check should also execute the dex instruction at the
         //     corresponding dex pc.
+        // If the ForceRetryInstruction bit is set this must be the second frame (the first being
+        // the one that is being popped).
         DCHECK_EQ(new_dex_pc, dex_pc);
+        shadow_frame->SetForceRetryInstruction(false);
       } else if (instr->Opcode() == Instruction::MONITOR_ENTER ||
                  instr->Opcode() == Instruction::MONITOR_EXIT) {
         DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
-        DCHECK(first);
+        DCHECK_EQ(frame_cnt, 0u);
         // Non-idempotent dex instruction should not be re-executed.
         // On the other hand, if a MONITOR_ENTER is at the dex_pc of a suspend
         // check, that MONITOR_ENTER should be executed. That case is handled
@@ -553,7 +565,7 @@
         DCHECK_EQ(new_dex_pc, dex_pc);
       } else {
         DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
-        DCHECK(first);
+        DCHECK_EQ(frame_cnt, 0u);
         // By default, we re-execute the dex instruction since if they are not
         // an invoke, so that we don't have to decode the dex instruction to move
         // result into the right vreg. All slow paths have been audited to be
@@ -566,7 +578,7 @@
     } else {
       // Nothing to do, the dex_pc is the one at which the code requested
       // the deoptimization.
-      DCHECK(first);
+      DCHECK_EQ(frame_cnt, 0u);
       DCHECK_EQ(new_dex_pc, dex_pc);
     }
     if (new_dex_pc != dex::kDexNoIndex) {
@@ -585,7 +597,7 @@
     // and should advance dex pc past the invoke instruction.
     from_code = false;
     deopt_method_type = DeoptimizationMethodType::kDefault;
-    first = false;
+    frame_cnt++;
   }
   ret_val->SetJ(value.GetJ());
 }
@@ -657,5 +669,18 @@
   InitMterpTls(self);
 }
 
+bool PrevFrameWillRetry(Thread* self, const ShadowFrame& frame) {
+  ShadowFrame* prev_frame = frame.GetLink();
+  if (prev_frame == nullptr) {
+    NthCallerVisitor vis(self, 1, false);
+    vis.WalkStack();
+    prev_frame = vis.GetCurrentShadowFrame();
+    if (prev_frame == nullptr) {
+      prev_frame = self->FindDebuggerShadowFrame(vis.GetFrameId());
+    }
+  }
+  return prev_frame != nullptr && prev_frame->GetForceRetryInstruction();
+}
+
 }  // namespace interpreter
 }  // namespace art
diff --git a/runtime/interpreter/interpreter.h b/runtime/interpreter/interpreter.h
index 0d43b90..d7e69a6 100644
--- a/runtime/interpreter/interpreter.h
+++ b/runtime/interpreter/interpreter.h
@@ -69,6 +69,12 @@
 
 void InitInterpreterTls(Thread* self);
 
+// Returns true if the previous frame has the ForceRetryInstruction bit set. This is required for
+// ForPopFrame to work correctly since that will cause the java function return with null/0 which
+// might not be expected by the code being run.
+bool PrevFrameWillRetry(Thread* self, const ShadowFrame& frame)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
 }  // namespace interpreter
 
 }  // namespace art
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 6577726..fe412bc 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -371,6 +371,12 @@
     if (UNLIKELY(self->IsExceptionPending())) {
       return false;
     }
+    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;
+    }
   }
   // Note: iput-x-quick instructions are only for non-volatile fields.
   switch (field_type) {
@@ -440,6 +446,11 @@
       self->IsExceptionThrownByCurrentMethod(exception.Get())) {
     // See b/65049545 for why we don't need to check to see if the exception has changed.
     instrumentation->ExceptionThrownEvent(self, exception.Get());
+    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.
+      return true;
+    }
   }
   bool clear_exception = false;
   uint32_t found_dex_pc = shadow_frame.GetMethod()->FindCatchBlock(
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 04935cf..cb64ff4 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -24,16 +24,39 @@
 #include "interpreter_common.h"
 #include "jit/jit.h"
 #include "jvalue-inl.h"
+#include "nth_caller_visitor.h"
 #include "safe_math.h"
 #include "shadow_frame-inl.h"
+#include "thread.h"
 
 namespace art {
 namespace interpreter {
 
+#define CHECK_FORCE_RETURN()                                                        \
+  do {                                                                              \
+    if (UNLIKELY(shadow_frame.GetForcePopFrame())) {                                \
+      DCHECK(PrevFrameWillRetry(self, shadow_frame))                                \
+          << "Pop frame forced without previous frame ready to retry instruction!"; \
+      DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());                     \
+      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. */                              \
+      return;                                                                       \
+    }                                                                               \
+  } while (false)
+
 #define HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instr)                                    \
   do {                                                                                          \
     DCHECK(self->IsExceptionPending());                                                         \
     self->AllowThreadSuspension();                                                              \
+    CHECK_FORCE_RETURN();                                                                       \
     if (!MoveToExceptionHandler(self, shadow_frame, instr)) {                                   \
       /* Structured locking is to be enforced for abnormal termination, too. */                 \
       DoMonitorCheckOnExit<do_assignability_check>(self, &shadow_frame);                        \
@@ -44,6 +67,7 @@
       ctx->result = JValue(); /* Handled in caller. */                                          \
       return;                                                                                   \
     } else {                                                                                    \
+      CHECK_FORCE_RETURN();                                                                     \
       int32_t displacement =                                                                    \
           static_cast<int32_t>(shadow_frame.GetDexPC()) - static_cast<int32_t>(dex_pc);         \
       inst = inst->RelativeAt(displacement);                                                    \
@@ -52,8 +76,39 @@
 
 #define HANDLE_PENDING_EXCEPTION() HANDLE_PENDING_EXCEPTION_WITH_INSTRUMENTATION(instrumentation)
 
+#define POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_IMPL(_is_exception_pending, _next_function) \
+  do {                                                                                          \
+    if (UNLIKELY(shadow_frame.GetForceRetryInstruction())) {                                    \
+      /* Don't need to do anything except clear the flag and exception. We leave the */         \
+      /* instruction the same so it will be re-executed on the next go-around.       */         \
+      DCHECK(inst->IsInvoke());                                                                 \
+      shadow_frame.SetForceRetryInstruction(false);                                             \
+      if (UNLIKELY(_is_exception_pending)) {                                                    \
+        DCHECK(self->IsExceptionPending());                                                     \
+        if (kIsDebugBuild) {                                                                    \
+          LOG(WARNING) << "Suppressing exception for instruction-retry: "                       \
+                       << self->GetException()->Dump();                                         \
+        }                                                                                       \
+        self->ClearException();                                                                 \
+      }                                                                                         \
+    } else if (UNLIKELY(_is_exception_pending)) {                                               \
+      /* Should have succeeded. */                                                              \
+      DCHECK(!shadow_frame.GetForceRetryInstruction());                                         \
+      HANDLE_PENDING_EXCEPTION();                                                               \
+    } else {                                                                                    \
+      inst = inst->_next_function();                                                            \
+    }                                                                                           \
+  } while (false)
+
+#define POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_POLYMORPHIC(_is_exception_pending) \
+  POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_IMPL(_is_exception_pending, Next_4xx)
+#define POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(_is_exception_pending) \
+  POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_IMPL(_is_exception_pending, Next_3xx)
+
 #define POSSIBLY_HANDLE_PENDING_EXCEPTION(_is_exception_pending, _next_function)  \
   do {                                                                            \
+    /* Should only be on invoke instructions. */                                  \
+    DCHECK(!shadow_frame.GetForceRetryInstruction());                             \
     if (UNLIKELY(_is_exception_pending)) {                                        \
       HANDLE_PENDING_EXCEPTION();                                                 \
     } else {                                                                      \
@@ -67,17 +122,22 @@
   }
 
 // Code to run before each dex instruction.
-#define PREAMBLE_SAVE(save_ref)                                                                      \
+#define PREAMBLE_SAVE(save_ref)                                                                 \
   {                                                                                             \
-    if (UNLIKELY(instrumentation->HasDexPcListeners()) &&                                       \
-        UNLIKELY(!DoDexPcMoveEvent(self,                                                        \
-                                   accessor,                                                    \
-                                   shadow_frame,                                                \
-                                   dex_pc,                                                      \
-                                   instrumentation,                                             \
-                                   save_ref))) {                                                \
-      HANDLE_PENDING_EXCEPTION();                                                               \
-      break;                                                                                    \
+    /* We need to put this before & after the instrumentation to avoid having to put in a */    \
+    /* post-script macro.                                                                 */    \
+    CHECK_FORCE_RETURN();                                                                       \
+    if (UNLIKELY(instrumentation->HasDexPcListeners())) {                                       \
+      if (UNLIKELY(!DoDexPcMoveEvent(self,                                                      \
+                                     accessor,                                                  \
+                                     shadow_frame,                                              \
+                                     dex_pc,                                                    \
+                                     instrumentation,                                           \
+                                     save_ref))) {                                              \
+        HANDLE_PENDING_EXCEPTION();                                                             \
+        break;                                                                                  \
+      }                                                                                         \
+      CHECK_FORCE_RETURN();                                                                     \
     }                                                                                           \
   }                                                                                             \
   do {} while (false)
@@ -181,7 +241,8 @@
                                            const JValue& result)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   bool had_event = false;
-  if (UNLIKELY(instrumentation->HasMethodExitListeners())) {
+  // 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.Ptr(), method, dex_pc, result);
   }
@@ -221,6 +282,9 @@
   uint16_t inst_data;
   jit::Jit* jit = Runtime::Current()->GetJit();
 
+  DCHECK(!shadow_frame.GetForceRetryInstruction())
+      << "Entered interpreter from invoke without retry instruction being handled!";
+
   do {
     dex_pc = inst->GetDexPc(insns);
     shadow_frame.SetDexPC(dex_pc);
@@ -1607,84 +1671,84 @@
         PREAMBLE();
         bool success = DoInvoke<kVirtual, false, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_VIRTUAL_RANGE: {
         PREAMBLE();
         bool success = DoInvoke<kVirtual, true, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_SUPER: {
         PREAMBLE();
         bool success = DoInvoke<kSuper, false, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_SUPER_RANGE: {
         PREAMBLE();
         bool success = DoInvoke<kSuper, true, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_DIRECT: {
         PREAMBLE();
         bool success = DoInvoke<kDirect, false, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_DIRECT_RANGE: {
         PREAMBLE();
         bool success = DoInvoke<kDirect, true, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_INTERFACE: {
         PREAMBLE();
         bool success = DoInvoke<kInterface, false, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_INTERFACE_RANGE: {
         PREAMBLE();
         bool success = DoInvoke<kInterface, true, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_STATIC: {
         PREAMBLE();
         bool success = DoInvoke<kStatic, false, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_STATIC_RANGE: {
         PREAMBLE();
         bool success = DoInvoke<kStatic, true, do_access_check>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_VIRTUAL_QUICK: {
         PREAMBLE();
         bool success = DoInvokeVirtualQuick<false>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: {
         PREAMBLE();
         bool success = DoInvokeVirtualQuick<true>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_POLYMORPHIC: {
@@ -1692,7 +1756,7 @@
         DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
         bool success = DoInvokePolymorphic<false /* is_range */>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_4xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_POLYMORPHIC(!success);
         break;
       }
       case Instruction::INVOKE_POLYMORPHIC_RANGE: {
@@ -1700,7 +1764,7 @@
         DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
         bool success = DoInvokePolymorphic<true /* is_range */>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_4xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE_POLYMORPHIC(!success);
         break;
       }
       case Instruction::INVOKE_CUSTOM: {
@@ -1708,7 +1772,7 @@
         DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
         bool success = DoInvokeCustom<false /* is_range */>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::INVOKE_CUSTOM_RANGE: {
@@ -1716,7 +1780,7 @@
         DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
         bool success = DoInvokeCustom<true /* is_range */>(
             self, shadow_frame, inst, inst_data, &result_register);
-        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION_ON_INVOKE(!success);
         break;
       }
       case Instruction::NEG_INT:
diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc
index fbc96f7..c385fb9 100644
--- a/runtime/interpreter/mterp/mterp.cc
+++ b/runtime/interpreter/mterp/mterp.cc
@@ -152,6 +152,11 @@
   const instrumentation::Instrumentation* const instrumentation = runtime->GetInstrumentation();
   return instrumentation->NonJitProfilingActive() ||
       Dbg::IsDebuggerActive() ||
+      // mterp only knows how to deal with the normal exits. It cannot handle any of the
+      // non-standard force-returns.
+      // TODO We really only need to switch interpreters if a PopFrame has actually happened. We
+      // should check this here.
+      UNLIKELY(runtime->AreNonStandardExitsEnabled()) ||
       // An async exception has been thrown. We need to go to the switch interpreter. MTerp doesn't
       // know how to deal with these so we could end up never dealing with it if we are in an
       // infinite loop. Since this can be called in a tight loop and getting the current thread
diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h
index 88eb413..c0920a8 100644
--- a/runtime/interpreter/shadow_frame.h
+++ b/runtime/interpreter/shadow_frame.h
@@ -49,6 +49,17 @@
 //  - interpreter - separate VRegs and reference arrays. References are in the reference array.
 //  - JNI - just VRegs, but where every VReg holds a reference.
 class ShadowFrame {
+ private:
+  // Used to keep track of extra state the shadowframe has.
+  enum class FrameFlags : uint32_t {
+    // We have been requested to notify when this frame gets popped.
+    kNotifyFramePop = 1 << 0,
+    // We have been asked to pop this frame off the stack as soon as possible.
+    kForcePopFrame  = 1 << 1,
+    // We have been asked to re-execute the last instruction.
+    kForceRetryInst = 1 << 2,
+  };
+
  public:
   // Compute size of ShadowFrame in bytes assuming it has a reference array.
   static size_t ComputeSize(uint32_t num_vregs) {
@@ -341,11 +352,27 @@
   }
 
   bool NeedsNotifyPop() const {
-    return needs_notify_pop_;
+    return GetFrameFlag(FrameFlags::kNotifyFramePop);
   }
 
   void SetNotifyPop(bool notify) {
-    needs_notify_pop_ = notify;
+    UpdateFrameFlag(notify, FrameFlags::kNotifyFramePop);
+  }
+
+  bool GetForcePopFrame() const {
+    return GetFrameFlag(FrameFlags::kForcePopFrame);
+  }
+
+  void SetForcePopFrame(bool enable) {
+    UpdateFrameFlag(enable, FrameFlags::kForcePopFrame);
+  }
+
+  bool GetForceRetryInstruction() const {
+    return GetFrameFlag(FrameFlags::kForceRetryInst);
+  }
+
+  void SetForceRetryInstruction(bool enable) {
+    UpdateFrameFlag(enable, FrameFlags::kForceRetryInst);
   }
 
  private:
@@ -360,7 +387,7 @@
         dex_pc_(dex_pc),
         cached_hotness_countdown_(0),
         hotness_countdown_(0),
-        needs_notify_pop_(0) {
+        frame_flags_(0) {
     // TODO(iam): Remove this parameter, it's an an artifact of portable removal
     DCHECK(has_reference_array);
     if (has_reference_array) {
@@ -370,6 +397,18 @@
     }
   }
 
+  void UpdateFrameFlag(bool enable, FrameFlags flag) {
+    if (enable) {
+      frame_flags_ |= static_cast<uint32_t>(flag);
+    } else {
+      frame_flags_ &= ~static_cast<uint32_t>(flag);
+    }
+  }
+
+  bool GetFrameFlag(FrameFlags flag) const {
+    return (frame_flags_ & static_cast<uint32_t>(flag)) != 0;
+  }
+
   const StackReference<mirror::Object>* References() const {
     DCHECK(HasReferenceArray());
     const uint32_t* vreg_end = &vregs_[NumberOfVRegs()];
@@ -393,9 +432,11 @@
   uint32_t dex_pc_;
   int16_t cached_hotness_countdown_;
   int16_t hotness_countdown_;
-  // TODO Might be worth it to try to bit-pack this into some other field to reduce stack usage.
-  // NB alignment requires that this field takes 4 bytes. Only 1 bit is actually ever used.
-  bool needs_notify_pop_;
+
+  // This is a set of ShadowFrame::FrameFlags which denote special states this frame is in.
+  // NB alignment requires that this field takes 4 bytes no matter its size. Only 3 bits are
+  // currently used.
+  uint32_t frame_flags_;
 
   // This is a two-part array:
   //  - [0..number_of_vregs) holds the raw virtual registers, and each element here is always 4
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index e882e73..36a6b7f 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -402,6 +402,8 @@
   bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
     exception_handler_->SetHandlerFrameDepth(GetFrameDepth());
     ArtMethod* method = GetMethod();
+    VLOG(deopt) << "Deoptimizing stack: depth: " << GetFrameDepth()
+                << " at method " << ArtMethod::PrettyMethod(method);
     if (method == nullptr || single_frame_done_) {
       FinishStackWalk();
       return false;  // End stack walk.
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 00d1d59..0271937 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -256,6 +256,7 @@
       is_native_bridge_loaded_(false),
       is_native_debuggable_(false),
       async_exceptions_thrown_(false),
+      non_standard_exits_enabled_(false),
       is_java_debuggable_(false),
       zygote_max_failed_boots_(0),
       experimental_flags_(ExperimentalFlags::kNone),
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 478ff50..398a48d 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -654,6 +654,14 @@
     is_native_debuggable_ = value;
   }
 
+  bool AreNonStandardExitsEnabled() const {
+    return non_standard_exits_enabled_;
+  }
+
+  void SetNonStandardExitsEnabled() {
+    non_standard_exits_enabled_ = true;
+  }
+
   bool AreAsyncExceptionsThrown() const {
     return async_exceptions_thrown_;
   }
@@ -986,6 +994,10 @@
   // MterpShouldSwitchInterpreters function.
   bool async_exceptions_thrown_;
 
+  // Whether anything is going to be using the shadow-frame APIs to force a function to return
+  // early. Doing this requires that (1) we be debuggable and (2) that mterp is exited.
+  bool non_standard_exits_enabled_;
+
   // Whether Java code needs to be debuggable.
   bool is_java_debuggable_;
 
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 0092b97..2fb63a1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -3368,11 +3368,34 @@
     HandleWrapperObjPtr<mirror::Throwable> h_exception(hs.NewHandleWrapper(&exception));
     instrumentation->ExceptionThrownEvent(this, exception.Ptr());
   }
-  // Does instrumentation need to deoptimize the stack?
-  // Note: we do this *after* reporting the exception to instrumentation in case it
-  // now requires deoptimization. It may happen if a debugger is attached and requests
-  // new events (single-step, breakpoint, ...) when the exception is reported.
-  if (Dbg::IsForcedInterpreterNeededForException(this)) {
+  // Does instrumentation need to deoptimize the stack or otherwise go to interpreter for something?
+  // Note: we do this *after* reporting the exception to instrumentation in case it now requires
+  // deoptimization. It may happen if a debugger is attached and requests new events (single-step,
+  // breakpoint, ...) when the exception is reported.
+  ShadowFrame* cf;
+  bool force_frame_pop = false;
+  {
+    NthCallerVisitor visitor(this, 0, false);
+    visitor.WalkStack();
+    cf = visitor.GetCurrentShadowFrame();
+    if (cf == nullptr) {
+      cf = FindDebuggerShadowFrame(visitor.GetFrameId());
+    }
+    force_frame_pop = cf != nullptr && cf->GetForcePopFrame();
+    if (kIsDebugBuild && force_frame_pop) {
+      NthCallerVisitor penultimate_visitor(this, 1, false);
+      penultimate_visitor.WalkStack();
+      ShadowFrame* penultimate_frame = penultimate_visitor.GetCurrentShadowFrame();
+      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");
+    }
+  }
+  if (Dbg::IsForcedInterpreterNeededForException(this) || force_frame_pop) {
     NthCallerVisitor visitor(this, 0, false);
     visitor.WalkStack();
     if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
@@ -3380,10 +3403,16 @@
       const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
       // Save the exception into the deoptimization context so it can be restored
       // before entering the interpreter.
+      if (force_frame_pop) {
+        VLOG(deopt) << "Deopting " << cf->GetMethod()->PrettyMethod() << " for frame-pop";
+        DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+        // Get rid of the exception since we are doing a framepop instead.
+        ClearException();
+      }
       PushDeoptimizationContext(
           JValue(),
           false /* is_reference */,
-          exception,
+          (force_frame_pop ? nullptr : exception),
           false /* from_code */,
           method_type);
       artDeoptimize(this);
diff --git a/test/1953-pop-frame/check b/test/1953-pop-frame/check
new file mode 100755
index 0000000..d552272
--- /dev/null
+++ b/test/1953-pop-frame/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/1953-pop-frame/class-loading-expected.patch b/test/1953-pop-frame/class-loading-expected.patch
new file mode 100644
index 0000000..2edef15
--- /dev/null
+++ b/test/1953-pop-frame/class-loading-expected.patch
@@ -0,0 +1,21 @@
+74a75,94
+> Test stopped during a ClassLoad event.
+> Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+> Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+> 	art.Test1953.popFrame(Native Method)
+> 	art.Test1953.runTestOn(Test1953.java)
+> 	art.Test1953.runTestOn(Test1953.java)
+> 	art.Test1953.runTests(Test1953.java)
+> 	<Additional frames hidden>
+> TC0.foo == 1
+> result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+> Test stopped during a ClassPrepare event.
+> Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+> Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+> 	art.Test1953.popFrame(Native Method)
+> 	art.Test1953.runTestOn(Test1953.java)
+> 	art.Test1953.runTestOn(Test1953.java)
+> 	art.Test1953.runTests(Test1953.java)
+> 	<Additional frames hidden>
+> TC1.foo == 2
+> result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1953-pop-frame/expected.txt b/test/1953-pop-frame/expected.txt
new file mode 100644
index 0000000..906703d
--- /dev/null
+++ b/test/1953-pop-frame/expected.txt
@@ -0,0 +1,98 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of calledFunction
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of doThrow
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
diff --git a/test/1953-pop-frame/info.txt b/test/1953-pop-frame/info.txt
new file mode 100644
index 0000000..b5eb546
--- /dev/null
+++ b/test/1953-pop-frame/info.txt
@@ -0,0 +1,7 @@
+Test basic JVMTI breakpoint functionality.
+
+This test places a breakpoint on the first instruction of a number of functions
+that are entered in every way possible for the given class of method.
+
+It also tests that breakpoints don't interfere with each other by having
+multiple breakpoints be set at once.
diff --git a/test/1953-pop-frame/pop_frame.cc b/test/1953-pop-frame/pop_frame.cc
new file mode 100644
index 0000000..1c2d2a1
--- /dev/null
+++ b/test/1953-pop-frame/pop_frame.cc
@@ -0,0 +1,998 @@
+/*
+ * 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"
+
+namespace art {
+namespace Test1953PopFrame {
+
+struct TestData {
+  jlocation target_loc;
+  jmethodID target_method;
+  jclass target_klass;
+  jfieldID target_field;
+  jrawMonitorID notify_monitor;
+  jint frame_pop_offset;
+  jmethodID frame_pop_setup_method;
+  std::vector<std::string> interesting_classes;
+  bool hit_location;
+
+  TestData(jvmtiEnv* jvmti,
+           JNIEnv* env,
+           jlocation loc,
+           jobject meth,
+           jclass klass,
+           jobject field,
+           jobject setup_meth,
+           jint pop_offset,
+           const std::vector<std::string>&& interesting)
+      : target_loc(loc),
+        target_method(meth != nullptr ? env->FromReflectedMethod(meth) : nullptr),
+        target_klass(reinterpret_cast<jclass>(env->NewGlobalRef(klass))),
+        target_field(field != nullptr ? env->FromReflectedField(field) : nullptr),
+        frame_pop_offset(pop_offset),
+        frame_pop_setup_method(setup_meth != nullptr ? env->FromReflectedMethod(setup_meth)
+                                                     : nullptr),
+        interesting_classes(interesting),
+        hit_location(false) {
+    JvmtiErrorToException(env, jvmti, jvmti->CreateRawMonitor("SuspendStopMonitor",
+                                                              &notify_monitor));
+  }
+
+  void PerformSuspend(jvmtiEnv* jvmti, JNIEnv* env) {
+    // Wake up the waiting thread.
+    JvmtiErrorToException(env, jvmti, jvmti->RawMonitorEnter(notify_monitor));
+    hit_location = true;
+    JvmtiErrorToException(env, jvmti, jvmti->RawMonitorNotifyAll(notify_monitor));
+    JvmtiErrorToException(env, jvmti, jvmti->RawMonitorExit(notify_monitor));
+    // Suspend ourself
+    jvmti->SuspendThread(nullptr);
+  }
+};
+
+void JNICALL cbSingleStep(jvmtiEnv* jvmti,
+                          JNIEnv* env,
+                          jthread thr,
+                          jmethodID meth,
+                          jlocation loc) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (meth != data->target_method || loc != data->target_loc) {
+    return;
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbExceptionCatch(jvmtiEnv *jvmti,
+                              JNIEnv* env,
+                              jthread thr,
+                              jmethodID method,
+                              jlocation location ATTRIBUTE_UNUSED,
+                              jobject exception ATTRIBUTE_UNUSED) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (method != data->target_method) {
+    return;
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbException(jvmtiEnv *jvmti,
+                         JNIEnv* env,
+                         jthread thr,
+                         jmethodID method,
+                         jlocation location ATTRIBUTE_UNUSED,
+                         jobject exception ATTRIBUTE_UNUSED,
+                         jmethodID catch_method ATTRIBUTE_UNUSED,
+                         jlocation catch_location ATTRIBUTE_UNUSED) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (method != data->target_method) {
+    return;
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbMethodEntry(jvmtiEnv *jvmti,
+                           JNIEnv* env,
+                           jthread thr,
+                           jmethodID method) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (method != data->target_method) {
+    return;
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbMethodExit(jvmtiEnv *jvmti,
+                          JNIEnv* env,
+                          jthread thr,
+                          jmethodID method,
+                          jboolean was_popped_by_exception ATTRIBUTE_UNUSED,
+                          jvalue return_value ATTRIBUTE_UNUSED) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (method != data->target_method) {
+    return;
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbFieldModification(jvmtiEnv* jvmti,
+                                 JNIEnv* env,
+                                 jthread thr,
+                                 jmethodID method ATTRIBUTE_UNUSED,
+                                 jlocation location ATTRIBUTE_UNUSED,
+                                 jclass field_klass ATTRIBUTE_UNUSED,
+                                 jobject object ATTRIBUTE_UNUSED,
+                                 jfieldID field,
+                                 char signature_type ATTRIBUTE_UNUSED,
+                                 jvalue new_value ATTRIBUTE_UNUSED) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (field != data->target_field) {
+    // TODO What to do here.
+    LOG(FATAL) << "Strange, shouldn't get here!";
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbFieldAccess(jvmtiEnv* jvmti,
+                           JNIEnv* env,
+                           jthread thr,
+                           jmethodID method ATTRIBUTE_UNUSED,
+                           jlocation location ATTRIBUTE_UNUSED,
+                           jclass field_klass,
+                           jobject object ATTRIBUTE_UNUSED,
+                           jfieldID field) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (field != data->target_field || !env->IsSameObject(field_klass, data->target_klass)) {
+    // TODO What to do here.
+    LOG(FATAL) << "Strange, shouldn't get here!";
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbBreakpointHit(jvmtiEnv* jvmti,
+                             JNIEnv* env,
+                             jthread thr,
+                             jmethodID method,
+                             jlocation loc) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (data->frame_pop_setup_method == method) {
+    CHECK(loc == 0) << "We should have stopped at location 0";
+    if (JvmtiErrorToException(env,
+                              jvmti,
+                              jvmti->NotifyFramePop(thr, data->frame_pop_offset))) {
+      return;
+    }
+    return;
+  }
+  if (method != data->target_method || loc != data->target_loc) {
+    // TODO What to do here.
+    LOG(FATAL) << "Strange, shouldn't get here!";
+  }
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbFramePop(jvmtiEnv* jvmti,
+                        JNIEnv* env,
+                        jthread thr,
+                        jmethodID method ATTRIBUTE_UNUSED,
+                        jboolean was_popped_by_exception ATTRIBUTE_UNUSED) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  data->PerformSuspend(jvmti, env);
+}
+
+void JNICALL cbClassLoadOrPrepare(jvmtiEnv* jvmti,
+                                  JNIEnv* env,
+                                  jthread thr,
+                                  jclass klass) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  char* name;
+  if (JvmtiErrorToException(env, jvmti, jvmti->GetClassSignature(klass, &name, nullptr))) {
+    return;
+  }
+  std::string name_str(name);
+  if (JvmtiErrorToException(env,
+                            jvmti,
+                            jvmti->Deallocate(reinterpret_cast<unsigned char*>(name)))) {
+    return;
+  }
+  if (std::find(data->interesting_classes.cbegin(),
+                data->interesting_classes.cend(),
+                name_str) != data->interesting_classes.cend()) {
+    data->PerformSuspend(jvmti, env);
+  }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupTest(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  jvmtiCapabilities caps;
+  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_generate_single_step_events        = 1;
+  caps.can_generate_breakpoint_events         = 1;
+  caps.can_suspend                            = 1;
+  caps.can_generate_method_entry_events       = 1;
+  caps.can_generate_method_exit_events        = 1;
+  caps.can_generate_monitor_events            = 1;
+  caps.can_generate_exception_events          = 1;
+  caps.can_generate_frame_pop_events          = 1;
+  caps.can_generate_field_access_events       = 1;
+  caps.can_generate_field_modification_events = 1;
+  caps.can_redefine_classes                   = 1;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
+    return;
+  }
+  jvmtiEventCallbacks cb;
+  memset(&cb, 0, sizeof(cb));
+  // TODO Add the rest of these.
+  cb.Breakpoint        = cbBreakpointHit;
+  cb.SingleStep        = cbSingleStep;
+  cb.FieldAccess       = cbFieldAccess;
+  cb.FieldModification = cbFieldModification;
+  cb.MethodEntry       = cbMethodEntry;
+  cb.MethodExit        = cbMethodExit;
+  cb.Exception         = cbException;
+  cb.ExceptionCatch    = cbExceptionCatch;
+  cb.FramePop          = cbFramePop;
+  cb.ClassLoad         = cbClassLoadOrPrepare;
+  cb.ClassPrepare      = cbClassLoadOrPrepare;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)));
+}
+
+static bool DeleteTestData(JNIEnv* env, jthread thr, TestData* data) {
+  env->DeleteGlobalRef(data->target_klass);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
+    return false;
+  }
+  return JvmtiErrorToException(env,
+                               jvmti_env,
+                               jvmti_env->Deallocate(reinterpret_cast<uint8_t*>(data)));
+}
+
+static TestData* SetupTestData(JNIEnv* env,
+                               jobject meth,
+                               jlocation loc,
+                               jclass target_klass,
+                               jobject field,
+                               jobject setup_meth,
+                               jint pop_offset,
+                               const std::vector<std::string>&& interesting_names) {
+  void* data_ptr;
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(TestData),
+                                                reinterpret_cast<uint8_t**>(&data_ptr)))) {
+    return nullptr;
+  }
+  data = new (data_ptr) TestData(jvmti_env,
+                                 env,
+                                 loc,
+                                 meth,
+                                 target_klass,
+                                 field,
+                                 setup_meth,
+                                 pop_offset,
+                                 std::move(interesting_names));
+  if (env->ExceptionCheck()) {
+    env->DeleteGlobalRef(data->target_klass);
+    jvmti_env->Deallocate(reinterpret_cast<uint8_t*>(data));
+    return nullptr;
+  }
+  return data;
+}
+
+static TestData* SetupTestData(JNIEnv* env,
+                               jobject meth,
+                               jlocation loc,
+                               jclass target_klass,
+                               jobject field,
+                               jobject setup_meth,
+                               jint pop_offset) {
+  std::vector<std::string> empty;
+  return SetupTestData(
+      env, meth, loc, target_klass, field, setup_meth, pop_offset, std::move(empty));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupSuspendClassEvent(JNIEnv* env,
+                                                      jclass klass ATTRIBUTE_UNUSED,
+                                                      jint event_num,
+                                                      jobjectArray interesting_names,
+                                                      jthread thr) {
+  CHECK(event_num == JVMTI_EVENT_CLASS_LOAD || event_num == JVMTI_EVENT_CLASS_PREPARE);
+  std::vector<std::string> names;
+  jint cnt = env->GetArrayLength(interesting_names);
+  for (jint i = 0; i < cnt; i++) {
+    env->PushLocalFrame(1);
+    jstring name_obj = reinterpret_cast<jstring>(env->GetObjectArrayElement(interesting_names, i));
+    const char* name_chr = env->GetStringUTFChars(name_obj, nullptr);
+    names.push_back(std::string(name_chr));
+    env->ReleaseStringUTFChars(name_obj, name_chr);
+    env->PopLocalFrame(nullptr);
+  }
+  TestData* data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, nullptr, 0, nullptr, nullptr, nullptr, 0, std::move(names));
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  JvmtiErrorToException(env,
+                        jvmti_env,
+                        jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                            static_cast<jvmtiEvent>(event_num),
+                                                            thr));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearSuspendClassEvent(JNIEnv* env,
+                                                      jclass klass ATTRIBUTE_UNUSED,
+                                                      jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_CLASS_LOAD,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_CLASS_PREPARE,
+                                                                thr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupSuspendSingleStepAt(JNIEnv* env,
+                                                        jclass klass ATTRIBUTE_UNUSED,
+                                                        jobject meth,
+                                                        jlocation loc,
+                                                        jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, meth, loc, nullptr, nullptr, nullptr, 0);
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                            JVMTI_EVENT_SINGLE_STEP,
+                                                                            thr));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearSuspendSingleStepFor(JNIEnv* env,
+                                                         jclass klass ATTRIBUTE_UNUSED,
+                                                         jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_SINGLE_STEP,
+                                                                thr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupSuspendPopFrameEvent(JNIEnv* env,
+                                                         jclass klass ATTRIBUTE_UNUSED,
+                                                         jint offset,
+                                                         jobject breakpoint_func,
+                                                         jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, nullptr, 0, nullptr, nullptr, breakpoint_func, offset);
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_FRAME_POP,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_BREAKPOINT,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetBreakpoint(data->frame_pop_setup_method, 0))) {
+    return;
+  }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearSuspendPopFrameEvent(JNIEnv* env,
+                                                         jclass klass ATTRIBUTE_UNUSED,
+                                                         jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_FRAME_POP,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_BREAKPOINT,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->ClearBreakpoint(data->frame_pop_setup_method, 0))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupSuspendBreakpointFor(JNIEnv* env,
+                                                         jclass klass ATTRIBUTE_UNUSED,
+                                                         jobject meth,
+                                                         jlocation loc,
+                                                         jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, meth, loc, nullptr, nullptr, nullptr, 0);
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                JVMTI_EVENT_BREAKPOINT,
+                                                                thr))) {
+    return;
+  }
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetBreakpoint(data->target_method,
+                                                                 data->target_loc));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearSuspendBreakpointFor(JNIEnv* env,
+                                                         jclass klass ATTRIBUTE_UNUSED,
+                                                         jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_BREAKPOINT,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->ClearBreakpoint(data->target_method,
+                                                       data->target_loc))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupSuspendExceptionEvent(JNIEnv* env,
+                                                          jclass klass ATTRIBUTE_UNUSED,
+                                                          jobject method,
+                                                          jboolean is_catch,
+                                                          jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(
+                                thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, method, 0, nullptr, nullptr, nullptr, 0);
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  JvmtiErrorToException(env,
+                        jvmti_env,
+                        jvmti_env->SetEventNotificationMode(
+                            JVMTI_ENABLE,
+                            is_catch ? JVMTI_EVENT_EXCEPTION_CATCH : JVMTI_EVENT_EXCEPTION,
+                            thr));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearSuspendExceptionEvent(JNIEnv* env,
+                                                          jclass klass ATTRIBUTE_UNUSED,
+                                                          jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_EXCEPTION_CATCH,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_EXCEPTION,
+                                                                thr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupSuspendMethodEvent(JNIEnv* env,
+                                                       jclass klass ATTRIBUTE_UNUSED,
+                                                       jobject method,
+                                                       jboolean enter,
+                                                       jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(
+                                thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, method, 0, nullptr, nullptr, nullptr, 0);
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  JvmtiErrorToException(env,
+                        jvmti_env,
+                        jvmti_env->SetEventNotificationMode(
+                            JVMTI_ENABLE,
+                            enter ? JVMTI_EVENT_METHOD_ENTRY : JVMTI_EVENT_METHOD_EXIT,
+                            thr));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearSuspendMethodEvent(JNIEnv* env,
+                                                       jclass klass ATTRIBUTE_UNUSED,
+                                                       jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_METHOD_EXIT,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_METHOD_ENTRY,
+                                                                thr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupFieldSuspendFor(JNIEnv* env,
+                                                    jclass klass ATTRIBUTE_UNUSED,
+                                                    jclass target_klass,
+                                                    jobject field,
+                                                    jboolean access,
+                                                    jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(
+                                thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, nullptr, 0, target_klass, field, nullptr, 0);
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(
+                                JVMTI_ENABLE,
+                                access ? JVMTI_EVENT_FIELD_ACCESS : JVMTI_EVENT_FIELD_MODIFICATION,
+                                thr))) {
+    return;
+  }
+  if (access) {
+    JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(data->target_klass,
+                                                                         data->target_field));
+  } else {
+    JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(data->target_klass,
+                                                                               data->target_field));
+  }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearFieldSuspendFor(JNIEnv* env,
+                                                    jclass klass ATTRIBUTE_UNUSED,
+                                                    jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_FIELD_ACCESS,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                                JVMTI_EVENT_FIELD_MODIFICATION,
+                                                                thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->ClearFieldModificationWatch(
+                                data->target_klass, data->target_field)) &&
+      JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->ClearFieldAccessWatch(
+                                data->target_klass, data->target_field))) {
+    return;
+  } else {
+    env->ExceptionClear();
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_setupWaitForNativeCall(JNIEnv* env,
+                                                      jclass klass ATTRIBUTE_UNUSED,
+                                                      jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(
+                                thr, reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data == nullptr) << "Data was not cleared!";
+  data = SetupTestData(env, nullptr, 0, nullptr, nullptr, nullptr, 0);
+  if (data == nullptr) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, data))) {
+    return;
+  }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_clearWaitForNativeCall(JNIEnv* env,
+                                                      jclass klass ATTRIBUTE_UNUSED,
+                                                      jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
+    return;
+  }
+  DeleteTestData(env, thr, data);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_waitForSuspendHit(JNIEnv* env,
+                                                 jclass klass ATTRIBUTE_UNUSED,
+                                                 jthread thr) {
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(thr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(data->notify_monitor))) {
+    return;
+  }
+  while (!data->hit_location) {
+    if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorWait(data->notify_monitor, -1))) {
+      return;
+    }
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(data->notify_monitor))) {
+    return;
+  }
+  jint state = 0;
+  while (!JvmtiErrorToException(env, jvmti_env, jvmti_env->GetThreadState(thr, &state)) &&
+         (state & JVMTI_THREAD_STATE_SUSPENDED) == 0) { }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_popFrame(JNIEnv* env,
+                                        jclass klass ATTRIBUTE_UNUSED,
+                                        jthread thr) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->PopFrame(thr));
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_00024NativeCalledObject_calledFunction(
+    JNIEnv* env, jobject thiz) {
+  env->PushLocalFrame(1);
+  jclass klass = env->GetObjectClass(thiz);
+  jfieldID cnt = env->GetFieldID(klass, "cnt", "I");
+  env->SetIntField(thiz, cnt, env->GetIntField(thiz, cnt) + 1);
+  env->PopLocalFrame(nullptr);
+  TestData *data;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->GetThreadLocalStorage(/* thread */ nullptr,
+                                                             reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  CHECK(data != nullptr);
+  data->PerformSuspend(jvmti_env, env);
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test1953_00024NativeCallerObject_run(
+    JNIEnv* env, jobject thiz) {
+  env->PushLocalFrame(1);
+  jclass klass = env->GetObjectClass(thiz);
+  jfieldID baseCnt = env->GetFieldID(klass, "baseCnt", "I");
+  env->SetIntField(thiz, baseCnt, env->GetIntField(thiz, baseCnt) + 1);
+  jmethodID called = env->GetMethodID(klass, "calledFunction", "()V");
+  env->CallVoidMethod(thiz, called);
+  env->PopLocalFrame(nullptr);
+}
+
+extern "C" JNIEXPORT
+jboolean JNICALL Java_art_Test1953_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 Test1953PopFrame
+}  // namespace art
+
diff --git a/test/1953-pop-frame/run b/test/1953-pop-frame/run
new file mode 100755
index 0000000..d16d4e6
--- /dev/null
+++ b/test/1953-pop-frame/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/1953-pop-frame/src/Main.java b/test/1953-pop-frame/src/Main.java
new file mode 100644
index 0000000..156076e
--- /dev/null
+++ b/test/1953-pop-frame/src/Main.java
@@ -0,0 +1,23 @@
+/*
+ * 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.Test1953.run(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS"));
+  }
+}
diff --git a/test/1953-pop-frame/src/art/Breakpoint.java b/test/1953-pop-frame/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1953-pop-frame/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/1953-pop-frame/src/art/Redefinition.java b/test/1953-pop-frame/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/1953-pop-frame/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/1953-pop-frame/src/art/StackTrace.java b/test/1953-pop-frame/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1953-pop-frame/src/art/StackTrace.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.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1953-pop-frame/src/art/Suspension.java b/test/1953-pop-frame/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1953-pop-frame/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1953-pop-frame/src/art/Test1953.java b/test/1953-pop-frame/src/art/Test1953.java
new file mode 100644
index 0000000..adec776
--- /dev/null
+++ b/test/1953-pop-frame/src/art/Test1953.java
@@ -0,0 +1,976 @@
+/*
+ * 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.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Base64;
+import java.util.EnumSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+public class Test1953 {
+  public final boolean canRunClassLoadTests;
+  public static void doNothing() {}
+
+  public interface TestRunnable extends Runnable {
+    public int getBaseCallCount();
+    public Method getCalledMethod() throws Exception;
+    public default Method getCallingMethod() throws Exception {
+      return this.getClass().getMethod("run");
+    };
+  }
+
+  public static interface TestSuspender {
+    public void setup(Thread thr);
+    public void waitForSuspend(Thread thr);
+    public void cleanup(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 setup(Thread thr) { setup.run(thr); }
+      public void waitForSuspend(Thread thr) { Test1953.waitForSuspendHit(thr); }
+      public void cleanup(Thread thr) { clean.run(thr); }
+    };
+  }
+
+  public void runTestOn(TestRunnable testObj, ThreadRunnable su, ThreadRunnable cl) throws
+      Exception {
+    runTestOn(testObj, makeSuspend(su, cl));
+  }
+
+  private static void SafePrintStackTrace(StackTraceElement st[]) {
+    for (StackTraceElement e : st) {
+      System.out.println("\t" + e.getClassName() + "." + e.getMethodName() + "(" +
+          (e.isNativeMethod() ? "Native Method" : e.getFileName()) + ")");
+      if (e.getClassName().equals("art.Test1953") && e.getMethodName().equals("runTests")) {
+        System.out.println("\t<Additional frames hidden>");
+        break;
+      }
+    }
+  }
+
+  public void runTestOn(TestRunnable testObj, TestSuspender su) throws Exception {
+    System.out.println("Single call with PopFrame on " + testObj + " base-call-count: " +
+        testObj.getBaseCallCount());
+    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 = new Thread(() -> { 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.setup(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.
+      popFrame(thr);
+    } catch (Exception e) {
+      System.out.println("Failed to pop frame 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 is " + testObj + " base-call count: " + testObj.getBaseCallCount());
+  }
+
+  public static abstract class AbstractTestObject implements TestRunnable {
+    public int callerCnt;
+
+    public AbstractTestObject() {
+      callerCnt = 0;
+    }
+
+    public int getBaseCallCount() {
+      return callerCnt;
+    }
+
+    public void run() {
+      callerCnt++;
+      // This function should be re-executed by the popFrame.
+      calledFunction();
+    }
+
+    public Method getCalledMethod() throws Exception {
+      return this.getClass().getMethod("calledFunction");
+    }
+
+    public abstract void calledFunction();
+  }
+
+  public static class RedefineTestObject extends AbstractTestObject implements Runnable {
+    public static enum RedefineState { ORIGINAL, REDEFINED, };
+    /* public static class RedefineTestObject extends AbstractTestObject implements Runnable {
+     *   public static final byte[] CLASS_BYTES;
+     *   public static final byte[] DEX_BYTES;
+     *   static {
+     *     CLASS_BYTES = null;
+     *     DEX_BYTES = null;
+     *   }
+     *
+     *   public EnumSet<RedefineState> redefine_states;
+     *   public RedefineTestObject() {
+     *     super();
+     *     redefine_states = EnumSet.noneOf(RedefineState.class);
+     *   }
+     *   public String toString() {
+     *     return "RedefineTestObject { states: " + redefine_states.toString()
+     *                                            + " current: REDEFINED }";
+     *   }
+     *   public void calledFunction() {
+     *     redefine_states.add(RedefineState.REDEFINED);  // line +0
+     *     // We will trigger the redefinition using a breakpoint on the next line.
+     *     doNothing();                                   // line +2
+     *   }
+     * }
+     */
+    public static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+      "yv66vgAAADUATQoADQAjBwAkCgAlACYJAAwAJwoAJQAoEgAAACwJAAIALQoAJQAuCgAvADAJAAwA" +
+      "MQkADAAyBwAzBwA0BwA2AQASUmVkZWZpbmVUZXN0T2JqZWN0AQAMSW5uZXJDbGFzc2VzAQANUmVk" +
+      "ZWZpbmVTdGF0ZQEAC0NMQVNTX0JZVEVTAQACW0IBAAlERVhfQllURVMBAA9yZWRlZmluZV9zdGF0" +
+      "ZXMBABNMamF2YS91dGlsL0VudW1TZXQ7AQAJU2lnbmF0dXJlAQBETGphdmEvdXRpbC9FbnVtU2V0" +
+      "PExhcnQvVGVzdDE5NTMkUmVkZWZpbmVUZXN0T2JqZWN0JFJlZGVmaW5lU3RhdGU7PjsBAAY8aW5p" +
+      "dD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAIdG9TdHJpbmcBABQoKUxqYXZhL2xh" +
+      "bmcvU3RyaW5nOwEADmNhbGxlZEZ1bmN0aW9uAQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQANVGVz" +
+      "dDE5NTMuamF2YQwAGQAaAQAtYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmlu" +
+      "ZVN0YXRlBwA3DAA4ADkMABUAFgwAHQAeAQAQQm9vdHN0cmFwTWV0aG9kcw8GADoIADsMADwAPQwA" +
+      "PgA/DABAAEEHAEIMAEMAGgwAEgATDAAUABMBAB9hcnQvVGVzdDE5NTMkUmVkZWZpbmVUZXN0T2Jq" +
+      "ZWN0AQAfYXJ0L1Rlc3QxOTUzJEFic3RyYWN0VGVzdE9iamVjdAEAEkFic3RyYWN0VGVzdE9iamVj" +
+      "dAEAEmphdmEvbGFuZy9SdW5uYWJsZQEAEWphdmEvdXRpbC9FbnVtU2V0AQAGbm9uZU9mAQAmKExq" +
+      "YXZhL2xhbmcvQ2xhc3M7KUxqYXZhL3V0aWwvRW51bVNldDsKAEQARQEAM1JlZGVmaW5lVGVzdE9i" +
+      "amVjdCB7IHN0YXRlczogASBjdXJyZW50OiBSRURFRklORUQgfQEAF21ha2VDb25jYXRXaXRoQ29u" +
+      "c3RhbnRzAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlSRURFRklO" +
+      "RUQBAC9MYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmluZVN0YXRlOwEAA2Fk" +
+      "ZAEAFShMamF2YS9sYW5nL09iamVjdDspWgEADGFydC9UZXN0MTk1MwEACWRvTm90aGluZwcARgwA" +
+      "PABJAQAkamF2YS9sYW5nL2ludm9rZS9TdHJpbmdDb25jYXRGYWN0b3J5BwBLAQAGTG9va3VwAQCY" +
+      "KExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5n" +
+      "O0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xh" +
+      "bmcvT2JqZWN0OylMamF2YS9sYW5nL2ludm9rZS9DYWxsU2l0ZTsHAEwBACVqYXZhL2xhbmcvaW52" +
+      "b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwAQAeamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVz" +
+      "ACEADAANAAEADgADABkAEgATAAAAGQAUABMAAAABABUAFgABABcAAAACABgABAABABkAGgABABsA" +
+      "AAAuAAIAAQAAAA4qtwABKhICuAADtQAEsQAAAAEAHAAAAA4AAwAAACEABAAiAA0AIwABAB0AHgAB" +
+      "ABsAAAAlAAEAAQAAAA0qtAAEtgAFugAGAACwAAAAAQAcAAAABgABAAAAJQABAB8AGgABABsAAAAv" +
+      "AAIAAQAAAA8qtAAEsgAHtgAIV7gACbEAAAABABwAAAAOAAMAAAApAAsAKwAOACwACAAgABoAAQAb" +
+      "AAAAKQABAAAAAAAJAbMACgGzAAuxAAAAAQAcAAAADgADAAAAGwAEABwACAAdAAMAIQAAAAIAIgAQ" +
+      "AAAAIgAEAAwALwAPAAkAAgAMABFAGQANAC8ANQQJAEcASgBIABkAKQAAAAgAAQAqAAEAKw==");
+    public static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+      "ZGV4CjAzNQAaR23N6WpunLRVX+BexSuzzNNiHNOvQpFoBwAAcAAAAHhWNBIAAAAAAAAAAKQGAAAq" +
+      "AAAAcAAAABEAAAAYAQAABQAAAFwBAAAEAAAAmAEAAAwAAAC4AQAAAQAAABgCAAAwBQAAOAIAACID" +
+      "AAA5AwAAQwMAAEsDAABPAwAAXAMAAGcDAABqAwAAbgMAAJEDAADCAwAA5QMAAPUDAAAZBAAAOQQA" +
+      "AFwEAAB7BAAAjgQAAKIEAAC4BAAAzAQAAOcEAAD8BAAAEQUAABwFAAAwBQAATwUAAF4FAABhBQAA" +
+      "ZAUAAGgFAABsBQAAeQUAAH4FAACGBQAAlgUAAKEFAACnBQAArwUAAMAFAADKBQAA0QUAAAgAAAAJ" +
+      "AAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAAGwAAABwA" +
+      "AAAeAAAABgAAAAsAAAAAAAAABwAAAAwAAAAMAwAABwAAAA0AAAAUAwAAGwAAAA4AAAAAAAAAHQAA" +
+      "AA8AAAAcAwAAAQABABcAAAACABAABAAAAAIAEAAFAAAAAgANACYAAAAAAAMAAgAAAAIAAwABAAAA" +
+      "AgADAAIAAAACAAMAIgAAAAIAAAAnAAAAAwADACMAAAAMAAMAAgAAAAwAAQAhAAAADAAAACcAAAAN" +
+      "AAQAIAAAAA0AAgAlAAAADQAAACcAAAACAAAAAQAAAAAAAAAEAwAAGgAAAIwGAABRBgAAAAAAAAQA" +
+      "AQACAAAA+gIAAB0AAABUMAMAbhALAAAADAAiAQwAcBAGAAEAGgIZAG4gBwAhAG4gBwABABoAAABu" +
+      "IAcAAQBuEAgAAQAMABEAAAABAAAAAAAAAPQCAAAGAAAAEgBpAAEAaQACAA4AAgABAAEAAADuAgAA" +
+      "DAAAAHAQAAABABwAAQBxEAoAAAAMAFsQAwAOAAMAAQACAAAA/gIAAAsAAABUIAMAYgEAAG4gCQAQ" +
+      "AHEABQAAAA4AIQAOPIcAGwAOPC0AJQAOACkADnk8AAEAAAAKAAAAAQAAAAsAAAABAAAACAAAAAEA" +
+      "AAAJABUgY3VycmVudDogUkVERUZJTkVEIH0ACDxjbGluaXQ+AAY8aW5pdD4AAj47AAtDTEFTU19C" +
+      "WVRFUwAJREVYX0JZVEVTAAFMAAJMTAAhTGFydC9UZXN0MTk1MyRBYnN0cmFjdFRlc3RPYmplY3Q7" +
+      "AC9MYXJ0L1Rlc3QxOTUzJFJlZGVmaW5lVGVzdE9iamVjdCRSZWRlZmluZVN0YXRlOwAhTGFydC9U" +
+      "ZXN0MTk1MyRSZWRlZmluZVRlc3RPYmplY3Q7AA5MYXJ0L1Rlc3QxOTUzOwAiTGRhbHZpay9hbm5v" +
+      "dGF0aW9uL0VuY2xvc2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ACFM" +
+      "ZGFsdmlrL2Fubm90YXRpb24vTWVtYmVyQ2xhc3NlczsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWdu" +
+      "YXR1cmU7ABFMamF2YS9sYW5nL0NsYXNzOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5n" +
+      "L1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7" +
+      "ABNMamF2YS91dGlsL0VudW1TZXQ7ABNMamF2YS91dGlsL0VudW1TZXQ8AAlSRURFRklORUQAElJl" +
+      "ZGVmaW5lVGVzdE9iamVjdAAdUmVkZWZpbmVUZXN0T2JqZWN0IHsgc3RhdGVzOiAADVRlc3QxOTUz" +
+      "LmphdmEAAVYAAVoAAlpMAAJbQgALYWNjZXNzRmxhZ3MAA2FkZAAGYXBwZW5kAA5jYWxsZWRGdW5j" +
+      "dGlvbgAJZG9Ob3RoaW5nAARuYW1lAAZub25lT2YAD3JlZGVmaW5lX3N0YXRlcwAIdG9TdHJpbmcA" +
+      "BXZhbHVlAFt+fkQ4eyJtaW4tYXBpIjoxLCJzaGEtMSI6IjUyNzNjM2RmZWUxMDQ2NzIwYWY0MjVm" +
+      "YTg1NTMxNmM5OWM4NmM4ZDIiLCJ2ZXJzaW9uIjoiMS4zLjE4LWRldiJ9AAIHASgcAxcWFwkXAwIE" +
+      "ASgYAwIFAh8ECSQXGAIGASgcARgBAgECAgEZARkDAQGIgASEBQGBgASgBQMByAUBAbgEAAAAAAAB" +
+      "AAAALgYAAAMAAAA6BgAAQAYAAEkGAAB8BgAAAQAAAAAAAAAAAAAAAwAAAHQGAAAQAAAAAAAAAAEA" +
+      "AAAAAAAAAQAAACoAAABwAAAAAgAAABEAAAAYAQAAAwAAAAUAAABcAQAABAAAAAQAAACYAQAABQAA" +
+      "AAwAAAC4AQAABgAAAAEAAAAYAgAAASAAAAQAAAA4AgAAAyAAAAQAAADuAgAAARAAAAQAAAAEAwAA" +
+      "AiAAACoAAAAiAwAABCAAAAQAAAAuBgAAACAAAAEAAABRBgAAAxAAAAMAAABwBgAABiAAAAEAAACM" +
+      "BgAAABAAAAEAAACkBgAA");
+
+    public EnumSet<RedefineState> redefine_states;
+    public RedefineTestObject() {
+      super();
+      redefine_states = EnumSet.noneOf(RedefineState.class);
+    }
+
+    public String toString() {
+      return "RedefineTestObject { states: " + redefine_states.toString() + " current: ORIGINAL }";
+    }
+
+    public void calledFunction() {
+      redefine_states.add(RedefineState.ORIGINAL);  // line +0
+      // We will trigger the redefinition using a breakpoint on the next line.
+      doNothing();                                  // line +2
+    }
+  }
+
+  public static class ClassLoadObject implements TestRunnable {
+    public int cnt;
+    public int baseCallCnt;
+
+    public static final String[] CLASS_NAMES = new String[] {
+      "Lart/Test1953$ClassLoadObject$TC0;",
+      "Lart/Test1953$ClassLoadObject$TC1;",
+      "Lart/Test1953$ClassLoadObject$TC2;",
+      "Lart/Test1953$ClassLoadObject$TC3;",
+      "Lart/Test1953$ClassLoadObject$TC4;",
+      "Lart/Test1953$ClassLoadObject$TC5;",
+      "Lart/Test1953$ClassLoadObject$TC6;",
+      "Lart/Test1953$ClassLoadObject$TC7;",
+      "Lart/Test1953$ClassLoadObject$TC8;",
+      "Lart/Test1953$ClassLoadObject$TC9;",
+    };
+
+    private static int curClass = 0;
+
+    private static class TC0 { public static int foo; static { foo = 1; } }
+    private static class TC1 { public static int foo; static { foo = 2; } }
+    private static class TC2 { public static int foo; static { foo = 3; } }
+    private static class TC3 { public static int foo; static { foo = 4; } }
+    private static class TC4 { public static int foo; static { foo = 5; } }
+    private static class TC5 { public static int foo; static { foo = 6; } }
+    private static class TC6 { public static int foo; static { foo = 7; } }
+    private static class TC7 { public static int foo; static { foo = 8; } }
+    private static class TC8 { public static int foo; static { foo = 9; } }
+    private static class TC9 { public static int foo; static { foo = 10; } }
+
+    public ClassLoadObject() {
+      super();
+      cnt = 0;
+      baseCallCnt = 0;
+    }
+
+    public int getBaseCallCount() {
+      return baseCallCnt;
+    }
+
+    public void run() {
+      baseCallCnt++;
+      if (curClass == 0) {
+        $noprecompile$calledFunction0();
+      } else if (curClass == 1) {
+        $noprecompile$calledFunction1();
+      } else if (curClass == 2) {
+        $noprecompile$calledFunction2();
+      } else if (curClass == 3) {
+        $noprecompile$calledFunction3();
+      } else if (curClass == 4) {
+        $noprecompile$calledFunction4();
+      } else if (curClass == 5) {
+        $noprecompile$calledFunction5();
+      } else if (curClass == 6) {
+        $noprecompile$calledFunction6();
+      } else if (curClass == 7) {
+        $noprecompile$calledFunction7();
+      } else if (curClass == 8) {
+        $noprecompile$calledFunction8();
+      } else if (curClass == 9) {
+        $noprecompile$calledFunction9();
+      }
+      curClass++;
+    }
+
+    public Method getCalledMethod() throws Exception {
+      return this.getClass().getMethod("jnoprecompile$calledFunction" + curClass);
+    }
+
+    // Give these all a tag to prevent 1954 from compiling them (and loading the class as a
+    // consequence).
+    public void $noprecompile$calledFunction0() {
+      cnt++;
+      System.out.println("TC0.foo == " + TC0.foo);
+    }
+
+    public void $noprecompile$calledFunction1() {
+      cnt++;
+      System.out.println("TC1.foo == " + TC1.foo);
+    }
+
+    public void $noprecompile$calledFunction2() {
+      cnt++;
+      System.out.println("TC2.foo == " + TC2.foo);
+    }
+
+    public void $noprecompile$calledFunction3() {
+      cnt++;
+      System.out.println("TC3.foo == " + TC3.foo);
+    }
+
+    public void $noprecompile$calledFunction4() {
+      cnt++;
+      System.out.println("TC4.foo == " + TC4.foo);
+    }
+
+    public void $noprecompile$calledFunction5() {
+      cnt++;
+      System.out.println("TC5.foo == " + TC5.foo);
+    }
+
+    public void $noprecompile$calledFunction6() {
+      cnt++;
+      System.out.println("TC6.foo == " + TC6.foo);
+    }
+
+    public void $noprecompile$calledFunction7() {
+      cnt++;
+      System.out.println("TC7.foo == " + TC7.foo);
+    }
+
+    public void $noprecompile$calledFunction8() {
+      cnt++;
+      System.out.println("TC8.foo == " + TC8.foo);
+    }
+
+    public void $noprecompile$calledFunction9() {
+      cnt++;
+      System.out.println("TC9.foo == " + TC9.foo);
+    }
+
+    public String toString() {
+      return "ClassLoadObject { cnt: " + cnt + ", curClass: " + curClass + "}";
+    }
+  }
+
+  public static class FieldBasedTestObject extends AbstractTestObject implements Runnable {
+    public int cnt;
+    public int TARGET_FIELD;
+    public FieldBasedTestObject() {
+      super();
+      cnt = 0;
+      TARGET_FIELD = 0;
+    }
+
+    public void calledFunction() {
+      cnt++;
+      // We put a watchpoint here and PopFrame when we are at it.
+      TARGET_FIELD += 10;
+      if (cnt == 1) { System.out.println("FAILED: No pop on first call!"); }
+    }
+
+    public String toString() {
+      return "FieldBasedTestObject { cnt: " + cnt + ", TARGET_FIELD: " + TARGET_FIELD + " }";
+    }
+  }
+
+  public static class StandardTestObject extends AbstractTestObject implements Runnable {
+    public int cnt;
+    public final boolean check;
+
+    public StandardTestObject(boolean check) {
+      super();
+      cnt = 0;
+      this.check = check;
+    }
+
+    public StandardTestObject() {
+      this(true);
+    }
+
+    public void calledFunction() {
+      cnt++;       // line +0
+      // We put a breakpoint here and PopFrame when we are at it.
+      doNothing(); // line +2
+      if (check && cnt == 1) { System.out.println("FAILED: No pop on first call!"); }
+    }
+
+    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
+    }
+
+    public String toString() {
+      return "SynchronizedFunctionTestObject { cnt: " + cnt + " }";
+    }
+  }
+  public static class SynchronizedTestObject extends AbstractTestObject implements Runnable {
+    public int cnt;
+    public final Object lock;
+
+    public SynchronizedTestObject() {
+      super();
+      cnt = 0;
+      lock = new Object();
+    }
+
+    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
+      }
+    }
+
+    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();
+      } catch (TestError e) {
+        System.out.println(e.getClass().getName() + " caught in called function.");
+      }
+    }
+
+    public void 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 ExceptionThrowFarTestObject(boolean catchInCalled) {
+      super();
+      cnt = 0;
+      baseCallCnt = 0;
+      this.catchInCalled = catchInCalled;
+    }
+
+    public int getBaseCallCount() {
+      return baseCallCnt;
+    }
+
+    public void run() {
+      baseCallCnt++;
+      try {
+        callingFunction();
+      } catch (TestError e) {
+        System.out.println(e.getClass().getName() + " thrown and caught!");
+      }
+    }
+
+    public void callingFunction() {
+      calledFunction();
+    }
+    public void calledFunction() {
+      cnt++;
+      if (catchInCalled) {
+        try {
+          throw new TestError(); // We put a watch here.
+        } catch (TestError e) {
+          System.out.println(e.getClass().getName() + " caught in same function.");
+        }
+      } else {
+        throw new TestError(); // We put a watch here.
+      }
+    }
+
+    public Method getCallingMethod() throws Exception {
+      return this.getClass().getMethod("callingFunction");
+    }
+
+    public Method getCalledMethod() throws Exception {
+      return this.getClass().getMethod("calledFunction");
+    }
+
+    public String toString() {
+      return "ExceptionThrowFarTestObject { cnt: " + cnt + " }";
+    }
+  }
+
+  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();
+        }
+      }
+    }
+
+    public void 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 int cnt;
+    public int baseCallCnt;
+    public final boolean catchInCalled;
+    public ExceptionThrowTestObject(boolean catchInCalled) {
+      super();
+      cnt = 0;
+      baseCallCnt = 0;
+      this.catchInCalled = catchInCalled;
+    }
+
+    public int getBaseCallCount() {
+      return baseCallCnt;
+    }
+
+    public void run() {
+      baseCallCnt++;
+      try {
+        calledFunction();
+      } catch (TestError e) {
+        System.out.println(e.getClass().getName() + " thrown and caught!");
+      }
+    }
+
+    public void calledFunction() {
+      cnt++;
+      if (catchInCalled) {
+        try {
+          throw new TestError(); // We put a watch here.
+        } catch (TestError e) {
+          System.out.println(e.getClass().getName() + " caught in same function.");
+        }
+      } else {
+        throw new TestError(); // We put a watch here.
+      }
+    }
+
+    public Method getCalledMethod() throws Exception {
+      return this.getClass().getMethod("calledFunction");
+    }
+
+    public String toString() {
+      return "ExceptionThrowTestObject { cnt: " + cnt + " }";
+    }
+  }
+
+  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 TestRunnable {
+    public int baseCnt = 0;
+    public int cnt = 0;
+
+    public int getBaseCallCount() {
+      return baseCnt;
+    }
+
+    public native void run();
+
+    public void calledFunction() {
+      cnt++;
+      // We will stop using a MethodExit event.
+    }
+
+    public Method getCalledMethod() throws Exception {
+      return this.getClass().getMethod("calledFunction");
+    }
+
+    public String toString() {
+      return "NativeCallerObject { cnt: " + cnt + " }";
+    }
+  }
+  public static class SuspendSuddenlyObject extends AbstractTestObject {
+    public volatile boolean stop_spinning = false;
+    public volatile boolean is_spinning = false;
+    public int cnt = 0;
+
+    public void calledFunction() {
+      cnt++;
+      while (!stop_spinning) {
+        is_spinning = true;
+      }
+    }
+
+    public String toString() {
+      return "SuspendSuddenlyObject { cnt: " + cnt + " }";
+    }
+  }
+
+  public static void run(boolean canRunClassLoadTests) throws Exception {
+    new Test1953(canRunClassLoadTests, (x)-> {}).runTests();
+  }
+
+  // This entrypoint is used by CTS only. */
+  public static void run() throws Exception {
+    /* TODO: Due to the way that CTS tests are verified we cannot run class-load-tests since the
+     *       verifier will be delayed until runtime and then load the classes all at once. This
+     *       makes the test impossible to run.
+     */
+    run(/*canRunClassLoadTests*/ false);
+  }
+
+  public Test1953(boolean canRunClassLoadTests, Consumer<TestRunnable> preTest) {
+    this.canRunClassLoadTests = canRunClassLoadTests;
+    this.preTest = preTest;
+  }
+
+  private Consumer<TestRunnable> preTest;
+
+  public void runTests() throws Exception {
+    setupTest();
+
+    final Method calledFunction = StandardTestObject.class.getDeclaredMethod("calledFunction");
+    final Method doNothingMethod = Test1953.class.getDeclaredMethod("doNothing");
+    // 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(new StandardTestObject(),
+        (thr) -> setupSuspendBreakpointFor(calledFunction, loc, thr),
+        Test1953::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(new SynchronizedFunctionTestObject(),
+        (thr) -> setupSuspendBreakpointFor(syncFunctionCalledFunction, syncFunctionLoc, thr),
+        Test1953::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(new SynchronizedTestObject(),
+        (thr) -> setupSuspendBreakpointFor(syncCalledFunction, syncLoc, thr),
+        Test1953::clearSuspendBreakpointFor);
+
+    System.out.println("Test stopped on single step");
+    runTestOn(new StandardTestObject(),
+        (thr) -> setupSuspendSingleStepAt(calledFunction, loc, thr),
+        Test1953::clearSuspendSingleStepFor);
+
+    final Field target_field = FieldBasedTestObject.class.getDeclaredField("TARGET_FIELD");
+    System.out.println("Test stopped on field access");
+    runTestOn(new FieldBasedTestObject(),
+        (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, true, thr),
+        Test1953::clearFieldSuspendFor);
+
+    System.out.println("Test stopped on field modification");
+    runTestOn(new FieldBasedTestObject(),
+        (thr) -> setupFieldSuspendFor(FieldBasedTestObject.class, target_field, false, thr),
+        Test1953::clearFieldSuspendFor);
+
+    System.out.println("Test stopped during Method Exit of doNothing");
+    runTestOn(new StandardTestObject(false),
+        (thr) -> setupSuspendMethodEvent(doNothingMethod, /*enter*/ false, thr),
+        Test1953::clearSuspendMethodEvent);
+
+    // NB We need another test to make sure the MethodEntered event is triggered twice.
+    System.out.println("Test stopped during Method Enter of doNothing");
+    runTestOn(new StandardTestObject(false),
+        (thr) -> setupSuspendMethodEvent(doNothingMethod, /*enter*/ true, thr),
+        Test1953::clearSuspendMethodEvent);
+
+    System.out.println("Test stopped during Method Exit of calledFunction");
+    runTestOn(new StandardTestObject(false),
+        (thr) -> setupSuspendMethodEvent(calledFunction, /*enter*/ false, thr),
+        Test1953::clearSuspendMethodEvent);
+
+    System.out.println("Test stopped during Method Enter of calledFunction");
+    runTestOn(new StandardTestObject(false),
+        (thr) -> setupSuspendMethodEvent(calledFunction, /*enter*/ true, thr),
+        Test1953::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),
+        Test1953::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),
+        Test1953::clearSuspendMethodEvent);
+
+    System.out.println("Test stopped during notifyFramePop without exception on pop of calledFunction");
+    runTestOn(new StandardTestObject(false),
+        (thr) -> setupSuspendPopFrameEvent(1, doNothingMethod, thr),
+        Test1953::clearSuspendPopFrameEvent);
+
+    System.out.println("Test stopped during notifyFramePop without exception on pop of doNothing");
+    runTestOn(new StandardTestObject(false),
+        (thr) -> setupSuspendPopFrameEvent(0, doNothingMethod, thr),
+        Test1953::clearSuspendPopFrameEvent);
+
+    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),
+        Test1953::clearSuspendPopFrameEvent);
+
+    final Method exceptionCatchThrowMethod =
+        ExceptionCatchTestObject.class.getDeclaredMethod("doThrow");
+    System.out.println("Test stopped during notifyFramePop with exception on pop of doThrow");
+    runTestOn(new ExceptionCatchTestObject(),
+        (thr) -> setupSuspendPopFrameEvent(0, exceptionCatchThrowMethod, thr),
+        Test1953::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),
+        Test1953::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(new ExceptionCatchTestObject(),
+        (thr) -> setupSuspendExceptionEvent(exceptionCatchCalledMethod, /*catch*/ true, thr),
+        Test1953::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),
+        Test1953::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),
+        Test1953::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),
+        Test1953::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),
+        Test1953::clearSuspendExceptionEvent);
+
+    // These tests are disabled for either the RI (b/116003018) or for jvmti-stress. For the
+    // later it is due to the additional agent causing classes to be loaded earlier as it forces
+    // deeper verification during class redefinition, causing failures.
+    // NB the agent is prevented from popping frames in either of these events in ART. See
+    // b/117615146 for more information about this restriction.
+    if (canRunClassLoadTests && CanRunClassLoadingTests()) {
+      // This test doesn't work on RI since the RI disallows use of PopFrame during a ClassLoad
+      // event. See b/116003018 for more information.
+      System.out.println("Test stopped during a ClassLoad event.");
+      runTestOn(new ClassLoadObject(),
+          (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_LOAD, ClassLoadObject.CLASS_NAMES, thr),
+          Test1953::clearSuspendClassEvent);
+
+      // The RI handles a PopFrame during a ClassPrepare event incorrectly. See b/116003018 for
+      // more information.
+      System.out.println("Test stopped during a ClassPrepare event.");
+      runTestOn(new ClassLoadObject(),
+          (thr) -> setupSuspendClassEvent(EVENT_TYPE_CLASS_PREPARE,
+                                          ClassLoadObject.CLASS_NAMES,
+                                          thr),
+          Test1953::clearSuspendClassEvent);
+    }
+    System.out.println("Test stopped during random Suspend.");
+    final SuspendSuddenlyObject sso = new SuspendSuddenlyObject();
+    runTestOn(
+        sso,
+        new TestSuspender() {
+          public void setup(Thread thr) { }
+          public void waitForSuspend(Thread thr) {
+            while (!sso.is_spinning) {}
+            Suspension.suspend(thr);
+          }
+          public void cleanup(Thread thr) {
+            sso.stop_spinning = true;
+          }
+        });
+
+    final Method redefineCalledFunction =
+       RedefineTestObject.class.getDeclaredMethod("calledFunction");
+    final int redefLine = Breakpoint.locationToLine(redefineCalledFunction, 0) + 2;
+    final long redefLoc = Breakpoint.lineToLocation(redefineCalledFunction, redefLine);
+    System.out.println("Test redefining frame being popped.");
+    runTestOn(new RedefineTestObject(),
+        (thr) -> setupSuspendBreakpointFor(redefineCalledFunction, redefLoc, thr),
+        (thr) -> {
+          clearSuspendBreakpointFor(thr);
+          Redefinition.doCommonClassRedefinition(RedefineTestObject.class,
+                                                 RedefineTestObject.CLASS_BYTES,
+                                                 RedefineTestObject.DEX_BYTES);
+        });
+
+    System.out.println("Test stopped during a native method fails");
+    runTestOn(new NativeCalledObject(),
+        Test1953::setupWaitForNativeCall,
+        Test1953::clearWaitForNativeCall);
+
+    System.out.println("Test stopped in a method called by native fails");
+    final Method nativeCallerMethod = NativeCallerObject.class.getDeclaredMethod("calledFunction");
+    runTestOn(new NativeCallerObject(),
+        (thr) -> setupSuspendMethodEvent(nativeCallerMethod, /*enter*/ false, thr),
+        Test1953::clearSuspendMethodEvent);
+  }
+
+  // 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/Test1953$RCLT1;");
+    ranClassLoadTest = true;
+    return classesPreverified;
+  }
+
+  public static native boolean isClassLoaded(String name);
+
+  public static native void setupTest();
+  public static native void popFrame(Thread thr);
+
+  public static native void setupSuspendBreakpointFor(Executable meth, long loc, Thread thr);
+  public static native void clearSuspendBreakpointFor(Thread thr);
+
+  public static native void setupSuspendSingleStepAt(Executable meth, long loc, Thread thr);
+  public static native void clearSuspendSingleStepFor(Thread thr);
+
+  public static native void setupFieldSuspendFor(Class klass, Field f, boolean access, Thread thr);
+  public static native void clearFieldSuspendFor(Thread thr);
+
+  public static native void setupSuspendMethodEvent(Executable meth, boolean enter, Thread thr);
+  public static native void clearSuspendMethodEvent(Thread thr);
+
+  public static native void setupSuspendExceptionEvent(
+      Executable meth, boolean is_catch, Thread thr);
+  public static native void clearSuspendExceptionEvent(Thread thr);
+
+  public static native void setupSuspendPopFrameEvent(
+      int offset, Executable breakpointFunction, Thread thr);
+  public static native void clearSuspendPopFrameEvent(Thread thr);
+
+  public static final int EVENT_TYPE_CLASS_LOAD = 55;
+  public static final int EVENT_TYPE_CLASS_PREPARE = 56;
+  public static native void setupSuspendClassEvent(
+      int eventType, String[] interestingNames, Thread thr);
+  public static native void clearSuspendClassEvent(Thread thr);
+
+  public static native void setupWaitForNativeCall(Thread thr);
+  public static native void clearWaitForNativeCall(Thread thr);
+
+  public static native void waitForSuspendHit(Thread thr);
+}
diff --git a/test/1954-pop-frame-jit/check b/test/1954-pop-frame-jit/check
new file mode 100755
index 0000000..10b87cc
--- /dev/null
+++ b/test/1954-pop-frame-jit/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 < jvm-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1954-pop-frame-jit/expected.txt b/test/1954-pop-frame-jit/expected.txt
new file mode 100644
index 0000000..a20a045
--- /dev/null
+++ b/test/1954-pop-frame-jit/expected.txt
@@ -0,0 +1,118 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of calledFunction
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of doThrow
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during a ClassLoad event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC0.foo == 1
+result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+Test stopped during a ClassPrepare event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC1.foo == 2
+result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
diff --git a/test/1954-pop-frame-jit/info.txt b/test/1954-pop-frame-jit/info.txt
new file mode 100644
index 0000000..b5eb546
--- /dev/null
+++ b/test/1954-pop-frame-jit/info.txt
@@ -0,0 +1,7 @@
+Test basic JVMTI breakpoint functionality.
+
+This test places a breakpoint on the first instruction of a number of functions
+that are entered in every way possible for the given class of method.
+
+It also tests that breakpoints don't interfere with each other by having
+multiple breakpoints be set at once.
diff --git a/test/1954-pop-frame-jit/jvm-expected.patch b/test/1954-pop-frame-jit/jvm-expected.patch
new file mode 100644
index 0000000..718f8ad
--- /dev/null
+++ b/test/1954-pop-frame-jit/jvm-expected.patch
@@ -0,0 +1,21 @@
+75,94d74
+< Test stopped during a ClassLoad event.
+< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+< 	art.Test1953.popFrame(Native Method)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTests(Test1953.java)
+< 	<Additional frames hidden>
+< TC0.foo == 1
+< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+< Test stopped during a ClassPrepare event.
+< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+< 	art.Test1953.popFrame(Native Method)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTests(Test1953.java)
+< 	<Additional frames hidden>
+< TC1.foo == 2
+< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1954-pop-frame-jit/run b/test/1954-pop-frame-jit/run
new file mode 100755
index 0000000..d16d4e6
--- /dev/null
+++ b/test/1954-pop-frame-jit/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/1954-pop-frame-jit/src/Main.java b/test/1954-pop-frame-jit/src/Main.java
new file mode 100644
index 0000000..12defcd
--- /dev/null
+++ b/test/1954-pop-frame-jit/src/Main.java
@@ -0,0 +1,60 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.time.Duration;
+
+import java.util.concurrent.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Stack;
+import java.util.Vector;
+
+import java.util.function.Supplier;
+
+import art.*;
+
+public class Main extends Test1953 {
+  public Main(boolean run_class_load_tests) {
+    super(run_class_load_tests, (testObj) -> {
+      try {
+        // Make sure everything is jitted in the method. We do this before calling setup since the
+        // suspend setup might make it impossible to jit the methods (by setting breakpoints or
+        // something).
+        for (Method m : testObj.getClass().getMethods()) {
+          if ((m.getModifiers() & Modifier.NATIVE) == 0 &&
+              !m.getName().startsWith("$noprecompile$")) {
+            ensureMethodJitCompiled(m);
+          }
+        }
+      } catch (Exception e) {}
+    });
+  }
+
+  public static void main(String[] args) throws Exception {
+    new Main(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")).runTests();
+  }
+
+  public static native void ensureMethodJitCompiled(Method meth);
+}
diff --git a/test/1954-pop-frame-jit/src/art/Breakpoint.java b/test/1954-pop-frame-jit/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1954-pop-frame-jit/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/1954-pop-frame-jit/src/art/Redefinition.java b/test/1954-pop-frame-jit/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/1954-pop-frame-jit/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/1954-pop-frame-jit/src/art/StackTrace.java b/test/1954-pop-frame-jit/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1954-pop-frame-jit/src/art/StackTrace.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.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1954-pop-frame-jit/src/art/Suspension.java b/test/1954-pop-frame-jit/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1954-pop-frame-jit/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1954-pop-frame-jit/src/art/Test1953.java b/test/1954-pop-frame-jit/src/art/Test1953.java
new file mode 120000
index 0000000..f281434
--- /dev/null
+++ b/test/1954-pop-frame-jit/src/art/Test1953.java
@@ -0,0 +1 @@
+../../../1953-pop-frame/src/art/Test1953.java
\ No newline at end of file
diff --git a/test/1955-pop-frame-jit-called/check b/test/1955-pop-frame-jit-called/check
new file mode 100755
index 0000000..10b87cc
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/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 < jvm-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1955-pop-frame-jit-called/expected.txt b/test/1955-pop-frame-jit-called/expected.txt
new file mode 100644
index 0000000..a20a045
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/expected.txt
@@ -0,0 +1,118 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of calledFunction
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of doThrow
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during a ClassLoad event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC0.foo == 1
+result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+Test stopped during a ClassPrepare event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC1.foo == 2
+result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
diff --git a/test/1955-pop-frame-jit-called/info.txt b/test/1955-pop-frame-jit-called/info.txt
new file mode 100644
index 0000000..b5eb546
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/info.txt
@@ -0,0 +1,7 @@
+Test basic JVMTI breakpoint functionality.
+
+This test places a breakpoint on the first instruction of a number of functions
+that are entered in every way possible for the given class of method.
+
+It also tests that breakpoints don't interfere with each other by having
+multiple breakpoints be set at once.
diff --git a/test/1955-pop-frame-jit-called/jvm-expected.patch b/test/1955-pop-frame-jit-called/jvm-expected.patch
new file mode 100644
index 0000000..718f8ad
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/jvm-expected.patch
@@ -0,0 +1,21 @@
+75,94d74
+< Test stopped during a ClassLoad event.
+< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+< 	art.Test1953.popFrame(Native Method)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTests(Test1953.java)
+< 	<Additional frames hidden>
+< TC0.foo == 1
+< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+< Test stopped during a ClassPrepare event.
+< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+< 	art.Test1953.popFrame(Native Method)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTests(Test1953.java)
+< 	<Additional frames hidden>
+< TC1.foo == 2
+< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1955-pop-frame-jit-called/run b/test/1955-pop-frame-jit-called/run
new file mode 100755
index 0000000..2984461
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/run
@@ -0,0 +1,26 @@
+#!/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
+
+# The jitthreshold prevents the jit from compiling anything except those which
+# we explicitly request.
+./default-run "$@" --android-runtime-option -Xjitthreshold:1000 --jvmti $ARGS
diff --git a/test/1955-pop-frame-jit-called/src/Main.java b/test/1955-pop-frame-jit-called/src/Main.java
new file mode 100644
index 0000000..30a42ea
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/src/Main.java
@@ -0,0 +1,53 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.time.Duration;
+
+import java.util.concurrent.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Stack;
+import java.util.Vector;
+
+import java.util.function.Supplier;
+
+import art.*;
+
+public class Main extends Test1953 {
+  public Main(boolean run_class_load_tests) {
+    super(run_class_load_tests, (testObj) -> {
+      try {
+        // Make sure the called method is jitted
+        ensureMethodJitCompiled(testObj.getCalledMethod());
+      } catch (Exception e) {}
+    });
+  }
+
+  public static void main(String[] args) throws Exception {
+    new Main(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")).runTests();
+  }
+
+  public static native void ensureMethodJitCompiled(Method meth);
+}
diff --git a/test/1955-pop-frame-jit-called/src/art/Breakpoint.java b/test/1955-pop-frame-jit-called/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/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/1955-pop-frame-jit-called/src/art/Redefinition.java b/test/1955-pop-frame-jit-called/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/1955-pop-frame-jit-called/src/art/StackTrace.java b/test/1955-pop-frame-jit-called/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/src/art/StackTrace.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.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1955-pop-frame-jit-called/src/art/Suspension.java b/test/1955-pop-frame-jit-called/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1955-pop-frame-jit-called/src/art/Test1953.java b/test/1955-pop-frame-jit-called/src/art/Test1953.java
new file mode 120000
index 0000000..f281434
--- /dev/null
+++ b/test/1955-pop-frame-jit-called/src/art/Test1953.java
@@ -0,0 +1 @@
+../../../1953-pop-frame/src/art/Test1953.java
\ No newline at end of file
diff --git a/test/1956-pop-frame-jit-calling/check b/test/1956-pop-frame-jit-calling/check
new file mode 100755
index 0000000..10b87cc
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/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 < jvm-expected.patch >/dev/null && ./default-check "$@")
diff --git a/test/1956-pop-frame-jit-calling/expected.txt b/test/1956-pop-frame-jit-calling/expected.txt
new file mode 100644
index 0000000..a20a045
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/expected.txt
@@ -0,0 +1,118 @@
+Test stopped using breakpoint
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with declared synchronized function
+Single call with PopFrame on SynchronizedFunctionTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedFunctionTestObject { cnt: 2 } base-call count: 1
+Test stopped using breakpoint with synchronized block
+Single call with PopFrame on SynchronizedTestObject { cnt: 0 } base-call-count: 0
+result is SynchronizedTestObject { cnt: 2 } base-call count: 1
+Test stopped on single step
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped on field access
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped on field modification
+Single call with PopFrame on FieldBasedTestObject { cnt: 0, TARGET_FIELD: 0 } base-call-count: 0
+result is FieldBasedTestObject { cnt: 2, TARGET_FIELD: 10 } base-call count: 1
+Test stopped during Method Exit of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Enter of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during Method Enter of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during Method Exit due to exception thrown in same function
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: false } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: false } base-call count: 1
+Test stopped during Method Exit due to exception thrown in subroutine
+Single call with PopFrame on ExceptionOnceObject { cnt: 0, throwInSub: true } base-call-count: 0
+result is ExceptionOnceObject { cnt: 2, throwInSub: true } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of calledFunction
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop without exception on pop of doNothing
+Single call with PopFrame on StandardTestObject { cnt: 0 } base-call-count: 0
+result is StandardTestObject { cnt: 1 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of calledFunction
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during notifyFramePop with exception on pop of doThrow
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 1 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during ExceptionCatch event of calledFunction (catch in called function, throw in subroutine)
+Single call with PopFrame on ExceptionCatchTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionCatchTestObject$TestError caught in called function.
+result is ExceptionCatchTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in calling function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError thrown and caught!
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowTestObject$TestError caught in same function.
+result is ExceptionThrowTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in parent of calling function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError thrown and caught!
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during Exception event of calledFunction (catch in called function)
+Single call with PopFrame on ExceptionThrowFarTestObject { cnt: 0 } base-call-count: 0
+art.Test1953$ExceptionThrowFarTestObject$TestError caught in same function.
+result is ExceptionThrowFarTestObject { cnt: 2 } base-call count: 1
+Test stopped during a ClassLoad event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC0.foo == 1
+result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+Test stopped during a ClassPrepare event.
+Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+TC1.foo == 2
+result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
+Test stopped during random Suspend.
+Single call with PopFrame on SuspendSuddenlyObject { cnt: 0 } base-call-count: 0
+result is SuspendSuddenlyObject { cnt: 2 } base-call count: 1
+Test redefining frame being popped.
+Single call with PopFrame on RedefineTestObject { states: [] current: ORIGINAL } base-call-count: 0
+result is RedefineTestObject { states: [ORIGINAL, REDEFINED] current: REDEFINED } base-call count: 1
+Test stopped during a native method fails
+Single call with PopFrame on NativeCalledObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCalledObject { cnt: 1 } base-call count: 1
+Test stopped in a method called by native fails
+Single call with PopFrame on NativeCallerObject { cnt: 0 } base-call-count: 0
+Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+	art.Test1953.popFrame(Native Method)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTestOn(Test1953.java)
+	art.Test1953.runTests(Test1953.java)
+	<Additional frames hidden>
+result is NativeCallerObject { cnt: 1 } base-call count: 1
diff --git a/test/1956-pop-frame-jit-calling/info.txt b/test/1956-pop-frame-jit-calling/info.txt
new file mode 100644
index 0000000..b5eb546
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/info.txt
@@ -0,0 +1,7 @@
+Test basic JVMTI breakpoint functionality.
+
+This test places a breakpoint on the first instruction of a number of functions
+that are entered in every way possible for the given class of method.
+
+It also tests that breakpoints don't interfere with each other by having
+multiple breakpoints be set at once.
diff --git a/test/1956-pop-frame-jit-calling/jvm-expected.patch b/test/1956-pop-frame-jit-calling/jvm-expected.patch
new file mode 100644
index 0000000..718f8ad
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/jvm-expected.patch
@@ -0,0 +1,21 @@
+75,94d74
+< Test stopped during a ClassLoad event.
+< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 0} base-call-count: 0
+< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+< 	art.Test1953.popFrame(Native Method)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTests(Test1953.java)
+< 	<Additional frames hidden>
+< TC0.foo == 1
+< result is ClassLoadObject { cnt: 1, curClass: 1} base-call count: 1
+< Test stopped during a ClassPrepare event.
+< Single call with PopFrame on ClassLoadObject { cnt: 0, curClass: 1} base-call-count: 0
+< Failed to pop frame due to java.lang.RuntimeException: JVMTI_ERROR_OPAQUE_FRAME
+< 	art.Test1953.popFrame(Native Method)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTestOn(Test1953.java)
+< 	art.Test1953.runTests(Test1953.java)
+< 	<Additional frames hidden>
+< TC1.foo == 2
+< result is ClassLoadObject { cnt: 1, curClass: 2} base-call count: 1
diff --git a/test/1956-pop-frame-jit-calling/run b/test/1956-pop-frame-jit-calling/run
new file mode 100755
index 0000000..2984461
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/run
@@ -0,0 +1,26 @@
+#!/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
+
+# The jitthreshold prevents the jit from compiling anything except those which
+# we explicitly request.
+./default-run "$@" --android-runtime-option -Xjitthreshold:1000 --jvmti $ARGS
diff --git a/test/1956-pop-frame-jit-calling/src/Main.java b/test/1956-pop-frame-jit-calling/src/Main.java
new file mode 100644
index 0000000..c44e035
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/src/Main.java
@@ -0,0 +1,53 @@
+/*
+ * 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.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.time.Duration;
+
+import java.util.concurrent.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Stack;
+import java.util.Vector;
+
+import java.util.function.Supplier;
+
+import art.*;
+
+public class Main extends Test1953 {
+  public Main(boolean run_class_load_tests) {
+    super(run_class_load_tests, (testObj) -> {
+      try {
+        // Make sure the calling method is jitted
+        ensureMethodJitCompiled(testObj.getCallingMethod());
+      } catch (Exception e) {}
+    });
+  }
+
+  public static void main(String[] args) throws Exception {
+    new Main(!Arrays.asList(args).contains("DISABLE_CLASS_LOAD_TESTS")).runTests();
+  }
+
+  public static native void ensureMethodJitCompiled(Method meth);
+}
diff --git a/test/1956-pop-frame-jit-calling/src/art/Breakpoint.java b/test/1956-pop-frame-jit-calling/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/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/1956-pop-frame-jit-calling/src/art/Redefinition.java b/test/1956-pop-frame-jit-calling/src/art/Redefinition.java
new file mode 100644
index 0000000..56d2938
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/src/art/Redefinition.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ArrayList;
+// Common Redefinition functions. Placed here for use by CTS
+public class Redefinition {
+  public static final class CommonClassDefinition {
+    public final Class<?> target;
+    public final byte[] class_file_bytes;
+    public final byte[] dex_file_bytes;
+
+    public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+      this.target = target;
+      this.class_file_bytes = class_file_bytes;
+      this.dex_file_bytes = dex_file_bytes;
+    }
+  }
+
+  // A set of possible test configurations. Test should set this if they need to.
+  // This must be kept in sync with the defines in ti-agent/common_helper.cc
+  public static enum Config {
+    COMMON_REDEFINE(0),
+    COMMON_RETRANSFORM(1),
+    COMMON_TRANSFORM(2);
+
+    private final int val;
+    private Config(int val) {
+      this.val = val;
+    }
+  }
+
+  public static void setTestConfiguration(Config type) {
+    nativeSetTestConfiguration(type.val);
+  }
+
+  private static native void nativeSetTestConfiguration(int type);
+
+  // Transforms the class
+  public static native void doCommonClassRedefinition(Class<?> target,
+                                                      byte[] classfile,
+                                                      byte[] dexfile);
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+  public static native void doCommonClassRetransformation(Class<?>... target);
+  public static native void setPopRetransformations(boolean pop);
+  public static native void popTransformationFor(String name);
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
+}
diff --git a/test/1956-pop-frame-jit-calling/src/art/StackTrace.java b/test/1956-pop-frame-jit-calling/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/src/art/StackTrace.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.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1956-pop-frame-jit-calling/src/art/Suspension.java b/test/1956-pop-frame-jit-calling/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1956-pop-frame-jit-calling/src/art/Test1953.java b/test/1956-pop-frame-jit-calling/src/art/Test1953.java
new file mode 120000
index 0000000..f281434
--- /dev/null
+++ b/test/1956-pop-frame-jit-calling/src/art/Test1953.java
@@ -0,0 +1 @@
+../../../1953-pop-frame/src/art/Test1953.java
\ No newline at end of file
diff --git a/test/Android.bp b/test/Android.bp
index 8f23058..8c1c1bf 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -291,6 +291,7 @@
         "1946-list-descriptors/descriptors.cc",
         "1950-unprepared-transform/unprepared_transform.cc",
         "1951-monitor-enter-no-suspend/raw_monitor.cc",
+        "1953-pop-frame/pop_frame.cc",
     ],
     // Use NDK-compatible headers for ctstiagent.
     header_libs: [
@@ -320,6 +321,7 @@
         "983-source-transform-verify/source_transform_art.cc",
         "1940-ddms-ext/ddm_ext.cc",
         "1944-sudden-exit/sudden_exit.cc",
+        // "1952-pop-frame-jit/pop_frame.cc",
     ],
     static_libs: [
         "libz",
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 4967834..65127fc 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -21,11 +21,13 @@
 
 #include "art_method-inl.h"
 #include "base/enums.h"
+#include "common_throws.h"
 #include "dex/dex_file-inl.h"
 #include "instrumentation.h"
 #include "jit/jit.h"
 #include "jit/jit_code_cache.h"
 #include "jit/profiling_info.h"
+#include "jni/jni_internal.h"
 #include "mirror/class-inl.h"
 #include "nativehelper/ScopedUtfChars.h"
 #include "oat_file.h"
@@ -195,6 +197,56 @@
   return jit->GetCodeCache()->ContainsMethod(method);
 }
 
+static void ForceJitCompiled(Thread* self, ArtMethod* method) REQUIRES(!Locks::mutator_lock_) {
+  {
+    ScopedObjectAccess soa(self);
+    if (method->IsNative()) {
+      std::string msg(method->PrettyMethod());
+      msg += ": is native";
+      ThrowIllegalArgumentException(msg.c_str());
+      return;
+    } else if (!Runtime::Current()->GetRuntimeCallbacks()->IsMethodSafeToJit(method)) {
+      std::string msg(method->PrettyMethod());
+      msg += ": is not safe to jit!";
+      ThrowIllegalStateException(msg.c_str());
+      return;
+    }
+  }
+  jit::Jit* jit = GetJitIfEnabled();
+  jit::JitCodeCache* code_cache = jit->GetCodeCache();
+  // Update the code cache to make sure the JIT code does not get deleted.
+  // Note: this will apply to all JIT compilations.
+  code_cache->SetGarbageCollectCode(false);
+  while (true) {
+    if (code_cache->WillExecuteJitCode(method)) {
+      break;
+    } else {
+      // Sleep to yield to the compiler thread.
+      usleep(1000);
+      ScopedObjectAccess soa(self);
+      // Make sure there is a profiling info, required by the compiler.
+      ProfilingInfo::Create(self, method, /* retry_allocation */ true);
+      // Will either ensure it's compiled or do the compilation itself.
+      jit->CompileMethod(method, self, /* osr */ false);
+    }
+  }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_ensureMethodJitCompiled(JNIEnv*, jclass, jobject meth) {
+  jit::Jit* jit = GetJitIfEnabled();
+  if (jit == nullptr) {
+    return;
+  }
+
+  Thread* self = Thread::Current();
+  ArtMethod* method;
+  {
+    ScopedObjectAccess soa(self);
+    method = ArtMethod::FromReflectedMethod(soa, meth);
+  }
+  ForceJitCompiled(self, method);
+}
+
 extern "C" JNIEXPORT void JNICALL Java_Main_ensureJitCompiled(JNIEnv* env,
                                                              jclass,
                                                              jclass cls,
@@ -219,24 +271,7 @@
     }
     DCHECK(method != nullptr) << "Unable to find method called " << chars.c_str();
   }
-
-  jit::JitCodeCache* code_cache = jit->GetCodeCache();
-  // Update the code cache to make sure the JIT code does not get deleted.
-  // Note: this will apply to all JIT compilations.
-  code_cache->SetGarbageCollectCode(false);
-  while (true) {
-    if (code_cache->WillExecuteJitCode(method)) {
-      break;
-    } else {
-      // Sleep to yield to the compiler thread.
-      usleep(1000);
-      ScopedObjectAccess soa(self);
-      // Make sure there is a profiling info, required by the compiler.
-      ProfilingInfo::Create(self, method, /* retry_allocation */ true);
-      // Will either ensure it's compiled or do the compilation itself.
-      jit->CompileMethod(method, self, /* osr */ false);
-    }
-  }
+  ForceJitCompiled(self, method);
 }
 
 extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasSingleImplementation(JNIEnv* env,
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 8aa5979..d769b48 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1090,6 +1090,14 @@
         "description": ["We do not inline with debuggable."]
     },
     {
+        "tests": ["1955-pop-frame-jit-called", "1956-pop-frame-jit-calling"],
+        "variant": "jit-on-first-use",
+        "description": [
+          "These tests directly set -Xjitthreshold:1000 to prevent the jit from compiling any",
+          "extra methods. jit-at-first-use would disrupt this."
+        ]
+    },
+    {
         "tests": ["135-MirandaDispatch"],
         "variant": "interp-ac & 32 & host",
         "env_vars": {"SANITIZE_HOST": "address"},
diff --git a/test/run-test b/test/run-test
index 4a4a64c..229e201 100755
--- a/test/run-test
+++ b/test/run-test
@@ -757,7 +757,7 @@
 echo "${test_dir}: building..." 1>&2
 
 rm -rf "$tmp_dir"
-cp -Rp "$test_dir" "$tmp_dir"
+cp -LRp "$test_dir" "$tmp_dir"
 cd "$tmp_dir"
 
 if [ '!' -r "$build" ]; then