Move thread plan stacks into the Process, indexed by TID.
authorJim Ingham <jingham@apple.com>
Tue, 10 Mar 2020 23:18:11 +0000 (16:18 -0700)
committerJim Ingham <jingham@apple.com>
Fri, 3 Apr 2020 21:56:28 +0000 (14:56 -0700)
Differential Revision: https://reviews.llvm.org/D75880

lldb/include/lldb/Target/Process.h
lldb/include/lldb/Target/Thread.h
lldb/include/lldb/Target/ThreadPlan.h
lldb/include/lldb/Target/ThreadPlanStack.h [new file with mode: 0644]
lldb/source/Target/CMakeLists.txt
lldb/source/Target/Process.cpp
lldb/source/Target/Thread.cpp
lldb/source/Target/ThreadList.cpp
lldb/source/Target/ThreadPlan.cpp
lldb/source/Target/ThreadPlanStack.cpp [new file with mode: 0644]

index 87f61c6..02987f9 100644 (file)
@@ -37,6 +37,7 @@
 #include "lldb/Target/Memory.h"
 #include "lldb/Target/QueueList.h"
 #include "lldb/Target/ThreadList.h"
+#include "lldb/Target/ThreadPlanStack.h"
 #include "lldb/Utility/ArchSpec.h"
 #include "lldb/Utility/Broadcaster.h"
 #include "lldb/Utility/Event.h"
@@ -2197,6 +2198,19 @@ public:
   }
 
   void SetDynamicCheckers(DynamicCheckerFunctions *dynamic_checkers);
+  
+  /// Find the thread plan stack associated with thread with \a tid.
+  ///
+  /// \param[in] tid
+  ///     The tid whose Plan Stack we are seeking..
+  ///
+  /// \return
+  ///     Returns a ThreadPlan if the TID is found or nullptr if not.
+  ThreadPlanStack *FindThreadPlans(lldb::tid_t tid);
+  
+  void AddThreadPlansForThread(Thread &thread);
+  
+  void RemoveThreadPlansForTID(lldb::tid_t tid);
 
   /// Call this to set the lldb in the mode where it breaks on new thread
   /// creations, and then auto-restarts.  This is useful when you are trying
@@ -2533,7 +2547,7 @@ protected:
     virtual EventActionResult HandleBeingInterrupted() = 0;
     virtual const char *GetExitString() = 0;
     void RequestResume() { m_process->m_resume_requested = true; }
-
+    
   protected:
     Process *m_process;
   };
@@ -2667,6 +2681,10 @@ protected:
                             ///see them. This is usually the same as
   ///< m_thread_list_real, but might be different if there is an OS plug-in
   ///creating memory threads
+  ThreadPlanStackMap m_thread_plans; ///< This is the list of thread plans for
+                                     /// threads in m_thread_list, as well as
+                                     /// threads we knew existed, but haven't
+                                     /// determined that they have died yet.
   ThreadList m_extended_thread_list; ///< Owner for extended threads that may be
                                      ///generated, cleared on natural stops
   uint32_t m_extended_thread_stop_id; ///< The natural stop id when
index b0bc1b2..dda303f 100644 (file)
@@ -28,6 +28,8 @@
 
 namespace lldb_private {
 
+class ThreadPlanStack;
+
 class ThreadProperties : public Properties {
 public:
   ThreadProperties(bool is_global);
@@ -119,7 +121,7 @@ public:
                            // bit of data.
     lldb::StopInfoSP stop_info_sp; // You have to restore the stop info or you
                                    // might continue with the wrong signals.
-    std::vector<lldb::ThreadPlanSP> m_completed_plan_stack;
+    size_t m_completed_plan_checkpoint;
     lldb::RegisterCheckpointSP
         register_backup_sp; // You need to restore the registers, of course...
     uint32_t current_inlined_depth;
@@ -912,7 +914,7 @@ public:
   ///
   /// \return
   ///     A pointer to the next executed plan.
-  ThreadPlan *GetCurrentPlan();
+  ThreadPlan *GetCurrentPlan() const;
 
   /// Unwinds the thread stack for the innermost expression plan currently
   /// on the thread plan stack.
@@ -927,14 +929,14 @@ public:
   ///
   /// \return
   ///     A pointer to the last completed plan.
-  lldb::ThreadPlanSP GetCompletedPlan();
+  lldb::ThreadPlanSP GetCompletedPlan() const;
 
   /// Gets the outer-most return value from the completed plans
   ///
   /// \return
   ///     A ValueObjectSP, either empty if there is no return value,
   ///     or containing the return value.
-  lldb::ValueObjectSP GetReturnValueObject();
+  lldb::ValueObjectSP GetReturnValueObject() const;
 
   /// Gets the outer-most expression variable from the completed plans
   ///
@@ -942,7 +944,7 @@ public:
   ///     A ExpressionVariableSP, either empty if there is no
   ///     plan completed an expression during the current stop
   ///     or the expression variable that was made for the completed expression.
-  lldb::ExpressionVariableSP GetExpressionVariable();
+  lldb::ExpressionVariableSP GetExpressionVariable() const;
 
   ///  Checks whether the given plan is in the completed plans for this
   ///  stop.
@@ -953,7 +955,7 @@ public:
   /// \return
   ///     Returns true if the input plan is in the completed plan stack,
   ///     false otherwise.
-  bool IsThreadPlanDone(ThreadPlan *plan);
+  bool IsThreadPlanDone(ThreadPlan *plan) const;
 
   ///  Checks whether the given plan is in the discarded plans for this
   ///  stop.
@@ -964,14 +966,14 @@ public:
   /// \return
   ///     Returns true if the input plan is in the discarded plan stack,
   ///     false otherwise.
-  bool WasThreadPlanDiscarded(ThreadPlan *plan);
+  bool WasThreadPlanDiscarded(ThreadPlan *plan) const;
 
   /// Check if we have completed plan to override breakpoint stop reason
   ///
   /// \return
   ///     Returns true if completed plan stack is not empty
   ///     false otherwise.
-  bool CompletedPlanOverridesBreakpoint();
+  bool CompletedPlanOverridesBreakpoint() const;
 
   /// Queues a generic thread plan.
   ///
@@ -1184,16 +1186,16 @@ protected:
   // thread is still in good shape to call virtual thread methods.  This must
   // be called by classes that derive from Thread in their destructor.
   virtual void DestroyThread();
+  
+  ThreadPlanStack &GetPlans() const;
 
-  void PushPlan(lldb::ThreadPlanSP &plan_sp);
+  void PushPlan(lldb::ThreadPlanSP plan_sp);
 
   void PopPlan();
 
   void DiscardPlan();
 
-  ThreadPlan *GetPreviousPlan(ThreadPlan *plan);
-
-  typedef std::vector<lldb::ThreadPlanSP> plan_stack;
+  ThreadPlan *GetPreviousPlan(ThreadPlan *plan) const;
 
   virtual Unwind &GetUnwinder();
 
@@ -1238,13 +1240,6 @@ protected:
   lldb::StateType m_state;                  ///< The state of our process.
   mutable std::recursive_mutex
       m_state_mutex;       ///< Multithreaded protection for m_state.
-  plan_stack m_plan_stack; ///< The stack of plans this thread is executing.
-  plan_stack m_completed_plan_stack; ///< Plans that have been completed by this
-                                     ///stop.  They get deleted when the thread
-                                     ///resumes.
-  plan_stack m_discarded_plan_stack; ///< Plans that have been discarded by this
-                                     ///stop.  They get deleted when the thread
-                                     ///resumes.
   mutable std::recursive_mutex
       m_frame_mutex; ///< Multithreaded protection for m_state.
   lldb::StackFrameListSP m_curr_frames_sp; ///< The stack frames that get lazily
@@ -1272,8 +1267,7 @@ private:
   StructuredData::ObjectSP m_extended_info; // The extended info for this thread
 
 private:
-  bool PlanIsBasePlan(ThreadPlan *plan_ptr);
-
+  
   void BroadcastSelectedFrameChange(StackID &new_frame_id);
 
   DISALLOW_COPY_AND_ASSIGN(Thread);
index 40c15e3..ef1e5f2 100644 (file)
@@ -371,9 +371,9 @@ public:
   ///   A  pointer to the thread plan's owning thread.
   Thread &GetThread();
 
-  Target &GetTarget() { return m_process.GetTarget(); }
+  Target &GetTarget();
 
-  const Target &GetTarget() const { return m_process.GetTarget(); }
+  const Target &GetTarget() const;
 
   /// Print a description of this thread to the stream \a s.
   /// \a thread.
diff --git a/lldb/include/lldb/Target/ThreadPlanStack.h b/lldb/include/lldb/Target/ThreadPlanStack.h
new file mode 100644 (file)
index 0000000..be7791a
--- /dev/null
@@ -0,0 +1,153 @@
+//===-- ThreadPlanStack.h ---------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_THREADPLANSTACK_H
+#define LLDB_TARGET_THREADPLANSTACK_H
+
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "lldb/Target/Target.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/lldb-private-forward.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+// The ThreadPlans have a thread for use when they are asked all the ThreadPlan
+// state machine questions, but they should never cache any pointers from their
+// owning lldb_private::Thread.  That's because we want to be able to detach
+// them from an owning thread, then reattach them by TID.
+// The ThreadPlanStack holds the ThreadPlans for a given TID.  All its methods
+// are private, and it should only be accessed through the owning thread.  When
+// it is detached from a thread, all you can do is reattach it or delete it.
+class ThreadPlanStack {
+  friend class lldb_private::Thread;
+
+public:
+  ThreadPlanStack(Thread &thread) {}
+  ~ThreadPlanStack() {}
+
+  enum StackKind { ePlans, eCompletedPlans, eDiscardedPlans };
+
+  using PlanStack = std::vector<lldb::ThreadPlanSP>;
+
+  void DumpThreadPlans(Stream *s, lldb::DescriptionLevel desc_level,
+                       bool include_internal) const;
+
+  size_t CheckpointCompletedPlans();
+
+  void RestoreCompletedPlanCheckpoint(size_t checkpoint);
+
+  void DiscardCompletedPlanCheckpoint(size_t checkpoint);
+
+  void ThreadDestroyed(Thread *thread);
+
+  void EnableTracer(bool value, bool single_stepping);
+
+  void SetTracer(lldb::ThreadPlanTracerSP &tracer_sp);
+
+  void PushPlan(lldb::ThreadPlanSP new_plan_sp);
+
+  lldb::ThreadPlanSP PopPlan();
+
+  lldb::ThreadPlanSP DiscardPlan();
+
+  // If the input plan is nullptr, discard all plans.  Otherwise make sure this
+  // plan is in the stack, and if so discard up to and including it.
+  void DiscardPlansUpToPlan(ThreadPlan *up_to_plan_ptr);
+
+  void DiscardAllPlans();
+
+  void DiscardConsultingMasterPlans();
+
+  lldb::ThreadPlanSP GetCurrentPlan() const;
+
+  lldb::ThreadPlanSP GetCompletedPlan(bool skip_private = true) const;
+
+  lldb::ThreadPlanSP GetPlanByIndex(uint32_t plan_idx,
+                                    bool skip_private = true) const;
+
+  lldb::ValueObjectSP GetReturnValueObject() const;
+
+  lldb::ExpressionVariableSP GetExpressionVariable() const;
+
+  bool AnyPlans() const;
+
+  bool AnyCompletedPlans() const;
+
+  bool AnyDiscardedPlans() const;
+
+  bool IsPlanDone(ThreadPlan *plan) const;
+
+  bool WasPlanDiscarded(ThreadPlan *plan) const;
+
+  ThreadPlan *GetPreviousPlan(ThreadPlan *current_plan) const;
+
+  ThreadPlan *GetInnermostExpression() const;
+
+  void WillResume();
+
+private:
+  const PlanStack &GetStackOfKind(ThreadPlanStack::StackKind kind) const;
+
+  PlanStack m_plans;           ///< The stack of plans this thread is executing.
+  PlanStack m_completed_plans; ///< Plans that have been completed by this
+                               /// stop.  They get deleted when the thread
+                               /// resumes.
+  PlanStack m_discarded_plans; ///< Plans that have been discarded by this
+                               /// stop.  They get deleted when the thread
+                               /// resumes.
+  size_t m_completed_plan_checkpoint = 0; // Monotonically increasing token for
+                                          // completed plan checkpoints.
+  std::unordered_map<size_t, PlanStack> m_completed_plan_store;
+};
+
+class ThreadPlanStackMap {
+public:
+  ThreadPlanStackMap() {}
+  ~ThreadPlanStackMap() {}
+
+  void AddThread(Thread &thread) {
+    lldb::tid_t tid = thread.GetID();
+    auto result = m_plans_list.emplace(tid, thread);
+  }
+
+  bool RemoveTID(lldb::tid_t tid) {
+    auto result = m_plans_list.find(tid);
+    if (result == m_plans_list.end())
+      return false;
+    result->second.ThreadDestroyed(nullptr);
+    m_plans_list.erase(result);
+    return true;
+  }
+
+  ThreadPlanStack *Find(lldb::tid_t tid) {
+    auto result = m_plans_list.find(tid);
+    if (result == m_plans_list.end())
+      return nullptr;
+    else
+      return &result->second;
+  }
+
+  void Clear() {
+    for (auto plan : m_plans_list)
+      plan.second.ThreadDestroyed(nullptr);
+    m_plans_list.clear();
+  }
+
+private:
+  using PlansList = std::unordered_map<lldb::tid_t, ThreadPlanStack>;
+  PlansList m_plans_list;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_THREADPLANSTACK_H
index 906dcb1..ca80b5b 100644 (file)
@@ -63,6 +63,7 @@ add_lldb_library(lldbTarget
   ThreadPlanStepThrough.cpp
   ThreadPlanStepUntil.cpp
   ThreadPlanTracer.cpp
+  ThreadPlanStack.cpp
   ThreadSpec.cpp
   UnixSignals.cpp
   UnwindAssembly.cpp
index 7dac2be..411160d 100644 (file)
@@ -60,6 +60,7 @@
 #include "lldb/Target/ThreadPlan.h"
 #include "lldb/Target/ThreadPlanBase.h"
 #include "lldb/Target/ThreadPlanCallFunction.h"
+#include "lldb/Target/ThreadPlanStack.h"
 #include "lldb/Target/UnixSignals.h"
 #include "lldb/Utility/Event.h"
 #include "lldb/Utility/Log.h"
@@ -600,6 +601,7 @@ void Process::Finalize() {
   m_system_runtime_up.reset();
   m_dyld_up.reset();
   m_jit_loaders_up.reset();
+  m_thread_plans.Clear();
   m_thread_list_real.Destroy();
   m_thread_list.Destroy();
   m_extended_thread_list.Destroy();
@@ -1252,6 +1254,20 @@ void Process::UpdateThreadListIfNeeded() {
   }
 }
 
+ThreadPlanStack *Process::FindThreadPlans(lldb::tid_t tid) {
+  return m_thread_plans.Find(tid);
+}
+
+void Process::AddThreadPlansForThread(Thread &thread) {
+  if (m_thread_plans.Find(thread.GetID()))
+    return;
+  m_thread_plans.AddThread(thread);
+}
+
+void Process::RemoveThreadPlansForTID(lldb::tid_t tid) {
+  m_thread_plans.RemoveTID(tid);
+}
+
 void Process::UpdateQueueListIfNeeded() {
   if (m_system_runtime_up) {
     if (m_queue_list.GetSize() == 0 ||
@@ -3231,6 +3247,10 @@ Status Process::Detach(bool keep_stopped) {
 }
 
 Status Process::Destroy(bool force_kill) {
+  // If we've already called Process::Finalize then there's nothing useful to
+  // be done here.  Finalize has actually called Destroy already.
+  if (m_finalize_called)
+    return {};
 
   // Tell ourselves we are in the process of destroying the process, so that we
   // don't do any unnecessary work that might hinder the destruction.  Remember
index d00542f..6b8edfb 100644 (file)
@@ -33,6 +33,7 @@
 #include "lldb/Target/ThreadPlanCallFunction.h"
 #include "lldb/Target/ThreadPlanPython.h"
 #include "lldb/Target/ThreadPlanRunToAddress.h"
+#include "lldb/Target/ThreadPlanStack.h"
 #include "lldb/Target/ThreadPlanStepInRange.h"
 #include "lldb/Target/ThreadPlanStepInstruction.h"
 #include "lldb/Target/ThreadPlanStepOut.h"
@@ -228,8 +229,7 @@ Thread::Thread(Process &process, lldb::tid_t tid, bool use_invalid_index_id)
       m_index_id(use_invalid_index_id ? LLDB_INVALID_INDEX32
                                       : process.GetNextThreadIndexID(tid)),
       m_reg_context_sp(), m_state(eStateUnloaded), m_state_mutex(),
-      m_plan_stack(), m_completed_plan_stack(), m_frame_mutex(),
-      m_curr_frames_sp(), m_prev_frames_sp(),
+      m_frame_mutex(), m_curr_frames_sp(), m_prev_frames_sp(),
       m_resume_signal(LLDB_INVALID_SIGNAL_NUMBER),
       m_resume_state(eStateRunning), m_temporary_resume_state(eStateRunning),
       m_unwinder_up(), m_destroy_called(false),
@@ -240,7 +240,9 @@ Thread::Thread(Process &process, lldb::tid_t tid, bool use_invalid_index_id)
             static_cast<void *>(this), GetID());
 
   CheckInWithManager();
-
+  
+  process.AddThreadPlansForThread(*this);
+  
   QueueFundamentalPlan(true);
 }
 
@@ -254,30 +256,7 @@ Thread::~Thread() {
 }
 
 void Thread::DestroyThread() {
-  // Tell any plans on the plan stacks that the thread is being destroyed since
-  // any plans that have a thread go away in the middle of might need to do
-  // cleanup, or in some cases NOT do cleanup...
-  for (auto plan : m_plan_stack)
-    plan->ThreadDestroyed();
-
-  for (auto plan : m_discarded_plan_stack)
-    plan->ThreadDestroyed();
-
-  for (auto plan : m_completed_plan_stack)
-    plan->ThreadDestroyed();
-
   m_destroy_called = true;
-  m_plan_stack.clear();
-  m_discarded_plan_stack.clear();
-  m_completed_plan_stack.clear();
-
-  // Push a ThreadPlanNull on the plan stack.  That way we can continue
-  // assuming that the plan stack is never empty, but if somebody errantly asks
-  // questions of a destroyed thread without checking first whether it is
-  // destroyed, they won't crash.
-  ThreadPlanSP null_plan_sp(new ThreadPlanNull(*this));
-  m_plan_stack.push_back(null_plan_sp);
-
   m_stop_info_sp.reset();
   m_reg_context_sp.reset();
   m_unwinder_up.reset();
@@ -522,7 +501,8 @@ bool Thread::CheckpointThreadState(ThreadStateCheckpoint &saved_state) {
   if (process_sp)
     saved_state.orig_stop_id = process_sp->GetStopID();
   saved_state.current_inlined_depth = GetCurrentInlinedDepth();
-  saved_state.m_completed_plan_stack = m_completed_plan_stack;
+  saved_state.m_completed_plan_checkpoint =
+      GetPlans().CheckpointCompletedPlans();
 
   return true;
 }
@@ -556,7 +536,8 @@ bool Thread::RestoreThreadStateFromCheckpoint(
   SetStopInfo(saved_state.stop_info_sp);
   GetStackFrameList()->SetCurrentInlinedDepth(
       saved_state.current_inlined_depth);
-  m_completed_plan_stack = saved_state.m_completed_plan_stack;
+  GetPlans().RestoreCompletedPlanCheckpoint(
+      saved_state.m_completed_plan_checkpoint);
   return true;
 }
 
@@ -689,8 +670,7 @@ void Thread::SetupForResume() {
 
 bool Thread::ShouldResume(StateType resume_state) {
   // At this point clear the completed plan stack.
-  m_completed_plan_stack.clear();
-  m_discarded_plan_stack.clear();
+  GetPlans().WillResume();
   m_override_should_notify = eLazyBoolCalculate;
 
   StateType prev_resume_state = GetTemporaryResumeState();
@@ -884,7 +864,7 @@ bool Thread::ShouldStop(Event *event_ptr) {
               current_plan->GetName(), over_ride_stop);
 
     // We're starting from the base plan, so just let it decide;
-    if (PlanIsBasePlan(current_plan)) {
+    if (current_plan->IsBasePlan()) {
       should_stop = current_plan->ShouldStop(event_ptr);
       LLDB_LOGF(log, "Base plan says should stop: %i.", should_stop);
     } else {
@@ -892,7 +872,7 @@ bool Thread::ShouldStop(Event *event_ptr) {
       // to do, since presumably if there were other plans they would know what
       // to do...
       while (true) {
-        if (PlanIsBasePlan(current_plan))
+        if (current_plan->IsBasePlan())
           break;
 
         should_stop = current_plan->ShouldStop(event_ptr);
@@ -938,7 +918,7 @@ bool Thread::ShouldStop(Event *event_ptr) {
 
     // Discard the stale plans and all plans below them in the stack, plus move
     // the completed plans to the completed plan stack
-    while (!PlanIsBasePlan(plan_ptr)) {
+    while (!plan_ptr->IsBasePlan()) {
       bool stale = plan_ptr->IsPlanStale();
       ThreadPlan *examined_plan = plan_ptr;
       plan_ptr = GetPreviousPlan(examined_plan);
@@ -1004,13 +984,14 @@ Vote Thread::ShouldReportStop(Event *event_ptr) {
     return eVoteNoOpinion;
   }
 
-  if (m_completed_plan_stack.size() > 0) {
-    // Don't use GetCompletedPlan here, since that suppresses private plans.
+  if (GetPlans().AnyCompletedPlans()) {
+    // Pass skip_private = false to GetCompletedPlan, since we want to ask 
+    // the last plan, regardless of whether it is private or not.
     LLDB_LOGF(log,
               "Thread::ShouldReportStop() tid = 0x%4.4" PRIx64
               ": returning vote  for complete stack's back plan",
               GetID());
-    return m_completed_plan_stack.back()->ShouldReportStop(event_ptr);
+    return GetPlans().GetCompletedPlan(false)->ShouldReportStop(event_ptr);
   } else {
     Vote thread_vote = eVoteNoOpinion;
     ThreadPlan *plan_ptr = GetCurrentPlan();
@@ -1019,7 +1000,7 @@ Vote Thread::ShouldReportStop(Event *event_ptr) {
         thread_vote = plan_ptr->ShouldReportStop(event_ptr);
         break;
       }
-      if (PlanIsBasePlan(plan_ptr))
+      if (plan_ptr->IsBasePlan())
         break;
       else
         plan_ptr = GetPreviousPlan(plan_ptr);
@@ -1041,16 +1022,17 @@ Vote Thread::ShouldReportRun(Event *event_ptr) {
   }
 
   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
-  if (m_completed_plan_stack.size() > 0) {
-    // Don't use GetCompletedPlan here, since that suppresses private plans.
+  if (GetPlans().AnyCompletedPlans()) {
+    // Pass skip_private = false to GetCompletedPlan, since we want to ask 
+    // the last plan, regardless of whether it is private or not.
     LLDB_LOGF(log,
               "Current Plan for thread %d(%p) (0x%4.4" PRIx64
               ", %s): %s being asked whether we should report run.",
               GetIndexID(), static_cast<void *>(this), GetID(),
               StateAsCString(GetTemporaryResumeState()),
-              m_completed_plan_stack.back()->GetName());
+              GetCompletedPlan()->GetName());
 
-    return m_completed_plan_stack.back()->ShouldReportRun(event_ptr);
+    return GetPlans().GetCompletedPlan(false)->ShouldReportRun(event_ptr);
   } else {
     LLDB_LOGF(log,
               "Current Plan for thread %d(%p) (0x%4.4" PRIx64
@@ -1067,148 +1049,75 @@ bool Thread::MatchesSpec(const ThreadSpec *spec) {
   return (spec == nullptr) ? true : spec->ThreadPassesBasicTests(*this);
 }
 
-void Thread::PushPlan(ThreadPlanSP &thread_plan_sp) {
-  if (thread_plan_sp) {
-    // If the thread plan doesn't already have a tracer, give it its parent's
-    // tracer:
-    if (!thread_plan_sp->GetThreadPlanTracer()) {
-      assert(!m_plan_stack.empty());
-      thread_plan_sp->SetThreadPlanTracer(
-          m_plan_stack.back()->GetThreadPlanTracer());
-    }
-    m_plan_stack.push_back(thread_plan_sp);
+ThreadPlanStack &Thread::GetPlans() const {
+  ThreadPlanStack *plans = GetProcess()->FindThreadPlans(GetID());
+  assert(plans && "Can't have a thread with no plans");
+  return *plans;
+}
 
-    thread_plan_sp->DidPush();
+void Thread::PushPlan(ThreadPlanSP thread_plan_sp) {
+  assert(thread_plan_sp && "Don't push an empty thread plan.");
 
-    Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
-    if (log) {
-      StreamString s;
-      thread_plan_sp->GetDescription(&s, lldb::eDescriptionLevelFull);
-      LLDB_LOGF(log, "Thread::PushPlan(0x%p): \"%s\", tid = 0x%4.4" PRIx64 ".",
-                static_cast<void *>(this), s.GetData(),
-                thread_plan_sp->GetThread().GetID());
-    }
+  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
+  if (log) {
+    StreamString s;
+    thread_plan_sp->GetDescription(&s, lldb::eDescriptionLevelFull);
+    LLDB_LOGF(log, "Thread::PushPlan(0x%p): \"%s\", tid = 0x%4.4" PRIx64 ".",
+              static_cast<void *>(this), s.GetData(),
+              thread_plan_sp->GetThread().GetID());
   }
+  
+  GetPlans().PushPlan(std::move(thread_plan_sp));
 }
 
 void Thread::PopPlan() {
   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
-
-  if (m_plan_stack.size() <= 1)
-    return;
-  else {
-    ThreadPlanSP &plan = m_plan_stack.back();
-    if (log) {
-      LLDB_LOGF(log, "Popping plan: \"%s\", tid = 0x%4.4" PRIx64 ".",
-                plan->GetName(), plan->GetThread().GetID());
-    }
-    m_completed_plan_stack.push_back(plan);
-    plan->WillPop();
-    m_plan_stack.pop_back();
+  ThreadPlanSP popped_plan_sp = GetPlans().PopPlan();
+  if (log) {
+    LLDB_LOGF(log, "Popping plan: \"%s\", tid = 0x%4.4" PRIx64 ".",
+              popped_plan_sp->GetName(), popped_plan_sp->GetThread().GetID());
   }
 }
 
 void Thread::DiscardPlan() {
   Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));
-  if (m_plan_stack.size() > 1) {
-    ThreadPlanSP &plan = m_plan_stack.back();
-    LLDB_LOGF(log, "Discarding plan: \"%s\", tid = 0x%4.4" PRIx64 ".",
-              plan->GetName(), plan->GetThread().GetID());
-
-    m_discarded_plan_stack.push_back(plan);
-    plan->WillPop();
-    m_plan_stack.pop_back();
-  }
-}
+  ThreadPlanSP discarded_plan_sp = GetPlans().PopPlan();
 
-ThreadPlan *Thread::GetCurrentPlan() {
-  // There will always be at least the base plan.  If somebody is mucking with
-  // a thread with an empty plan stack, we should assert right away.
-  return m_plan_stack.empty() ? nullptr : m_plan_stack.back().get();
+  LLDB_LOGF(log, "Discarding plan: \"%s\", tid = 0x%4.4" PRIx64 ".",
+            discarded_plan_sp->GetName(), 
+            discarded_plan_sp->GetThread().GetID());
 }
 
-ThreadPlanSP Thread::GetCompletedPlan() {
-  ThreadPlanSP empty_plan_sp;
-  if (!m_completed_plan_stack.empty()) {
-    for (int i = m_completed_plan_stack.size() - 1; i >= 0; i--) {
-      ThreadPlanSP completed_plan_sp;
-      completed_plan_sp = m_completed_plan_stack[i];
-      if (!completed_plan_sp->GetPrivate())
-        return completed_plan_sp;
-    }
-  }
-  return empty_plan_sp;
+ThreadPlan *Thread::GetCurrentPlan() const {
+  return GetPlans().GetCurrentPlan().get();
 }
 
-ValueObjectSP Thread::GetReturnValueObject() {
-  if (!m_completed_plan_stack.empty()) {
-    for (int i = m_completed_plan_stack.size() - 1; i >= 0; i--) {
-      ValueObjectSP return_valobj_sp;
-      return_valobj_sp = m_completed_plan_stack[i]->GetReturnValueObject();
-      if (return_valobj_sp)
-        return return_valobj_sp;
-    }
-  }
-  return ValueObjectSP();
+ThreadPlanSP Thread::GetCompletedPlan() const {
+  return GetPlans().GetCompletedPlan();
 }
 
-ExpressionVariableSP Thread::GetExpressionVariable() {
-  if (!m_completed_plan_stack.empty()) {
-    for (int i = m_completed_plan_stack.size() - 1; i >= 0; i--) {
-      ExpressionVariableSP expression_variable_sp;
-      expression_variable_sp =
-          m_completed_plan_stack[i]->GetExpressionVariable();
-      if (expression_variable_sp)
-        return expression_variable_sp;
-    }
-  }
-  return ExpressionVariableSP();
+ValueObjectSP Thread::GetReturnValueObject() const {
+  return GetPlans().GetReturnValueObject();
 }
 
-bool Thread::IsThreadPlanDone(ThreadPlan *plan) {
-  if (!m_completed_plan_stack.empty()) {
-    for (int i = m_completed_plan_stack.size() - 1; i >= 0; i--) {
-      if (m_completed_plan_stack[i].get() == plan)
-        return true;
-    }
-  }
-  return false;
+ExpressionVariableSP Thread::GetExpressionVariable() const {
+  return GetPlans().GetExpressionVariable();
 }
 
-bool Thread::WasThreadPlanDiscarded(ThreadPlan *plan) {
-  if (!m_discarded_plan_stack.empty()) {
-    for (int i = m_discarded_plan_stack.size() - 1; i >= 0; i--) {
-      if (m_discarded_plan_stack[i].get() == plan)
-        return true;
-    }
-  }
-  return false;
+bool Thread::IsThreadPlanDone(ThreadPlan *plan) const {
+  return GetPlans().IsPlanDone(plan);
 }
 
-bool Thread::CompletedPlanOverridesBreakpoint() {
-  return (!m_completed_plan_stack.empty()) ;
+bool Thread::WasThreadPlanDiscarded(ThreadPlan *plan) const {
+  return GetPlans().WasPlanDiscarded(plan);
 }
 
-ThreadPlan *Thread::GetPreviousPlan(ThreadPlan *current_plan) {
-  if (current_plan == nullptr)
-    return nullptr;
-
-  int stack_size = m_completed_plan_stack.size();
-  for (int i = stack_size - 1; i > 0; i--) {
-    if (current_plan == m_completed_plan_stack[i].get())
-      return m_completed_plan_stack[i - 1].get();
-  }
-
-  if (stack_size > 0 && m_completed_plan_stack[0].get() == current_plan) {
-    return GetCurrentPlan();
-  }
+bool Thread::CompletedPlanOverridesBreakpoint() const {
+  return GetPlans().AnyCompletedPlans();
+}
 
-  stack_size = m_plan_stack.size();
-  for (int i = stack_size - 1; i > 0; i--) {
-    if (current_plan == m_plan_stack[i].get())
-      return m_plan_stack[i - 1].get();
-  }
-  return nullptr;
+ThreadPlan *Thread::GetPreviousPlan(ThreadPlan *current_plan) const{
+  return GetPlans().GetPreviousPlan(current_plan);
 }
 
 Status Thread::QueueThreadPlan(ThreadPlanSP &thread_plan_sp,
@@ -1242,38 +1151,18 @@ Status Thread::QueueThreadPlan(ThreadPlanSP &thread_plan_sp,
 }
 
 void Thread::EnableTracer(bool value, bool single_stepping) {
-  int stack_size = m_plan_stack.size();
-  for (int i = 0; i < stack_size; i++) {
-    if (m_plan_stack[i]->GetThreadPlanTracer()) {
-      m_plan_stack[i]->GetThreadPlanTracer()->EnableTracing(value);
-      m_plan_stack[i]->GetThreadPlanTracer()->EnableSingleStep(single_stepping);
-    }
-  }
+  GetPlans().EnableTracer(value, single_stepping);
 }
 
 void Thread::SetTracer(lldb::ThreadPlanTracerSP &tracer_sp) {
-  int stack_size = m_plan_stack.size();
-  for (int i = 0; i < stack_size; i++)
-    m_plan_stack[i]->SetThreadPlanTracer(tracer_sp);
+  GetPlans().SetTracer(tracer_sp);
 }
 
-bool Thread::DiscardUserThreadPlansUpToIndex(uint32_t thread_index) {
+bool Thread::DiscardUserThreadPlansUpToIndex(uint32_t plan_index) {
   // Count the user thread plans from the back end to get the number of the one
   // we want to discard:
 
-  uint32_t idx = 0;
-  ThreadPlan *up_to_plan_ptr = nullptr;
-
-  for (ThreadPlanSP plan_sp : m_plan_stack) {
-    if (plan_sp->GetPrivate())
-      continue;
-    if (idx == thread_index) {
-      up_to_plan_ptr = plan_sp.get();
-      break;
-    } else
-      idx++;
-  }
-
+  ThreadPlan *up_to_plan_ptr = GetPlans().GetPlanByIndex(plan_index).get();
   if (up_to_plan_ptr == nullptr)
     return false;
 
@@ -1291,30 +1180,7 @@ void Thread::DiscardThreadPlansUpToPlan(ThreadPlan *up_to_plan_ptr) {
             "Discarding thread plans for thread tid = 0x%4.4" PRIx64
             ", up to %p",
             GetID(), static_cast<void *>(up_to_plan_ptr));
-
-  int stack_size = m_plan_stack.size();
-
-  // If the input plan is nullptr, discard all plans.  Otherwise make sure this
-  // plan is in the stack, and if so discard up to and including it.
-
-  if (up_to_plan_ptr == nullptr) {
-    for (int i = stack_size - 1; i > 0; i--)
-      DiscardPlan();
-  } else {
-    bool found_it = false;
-    for (int i = stack_size - 1; i > 0; i--) {
-      if (m_plan_stack[i].get() == up_to_plan_ptr)
-        found_it = true;
-    }
-    if (found_it) {
-      bool last_one = false;
-      for (int i = stack_size - 1; i > 0 && !last_one; i--) {
-        if (GetCurrentPlan() == up_to_plan_ptr)
-          last_one = true;
-        DiscardPlan();
-      }
-    }
-  }
+  GetPlans().DiscardPlansUpToPlan(up_to_plan_ptr);
 }
 
 void Thread::DiscardThreadPlans(bool force) {
@@ -1327,73 +1193,20 @@ void Thread::DiscardThreadPlans(bool force) {
   }
 
   if (force) {
-    int stack_size = m_plan_stack.size();
-    for (int i = stack_size - 1; i > 0; i--) {
-      DiscardPlan();
-    }
+    GetPlans().DiscardAllPlans();
     return;
   }
-
-  while (true) {
-    int master_plan_idx;
-    bool discard = true;
-
-    // Find the first master plan, see if it wants discarding, and if yes
-    // discard up to it.
-    for (master_plan_idx = m_plan_stack.size() - 1; master_plan_idx >= 0;
-         master_plan_idx--) {
-      if (m_plan_stack[master_plan_idx]->IsMasterPlan()) {
-        discard = m_plan_stack[master_plan_idx]->OkayToDiscard();
-        break;
-      }
-    }
-
-    if (discard) {
-      // First pop all the dependent plans:
-      for (int i = m_plan_stack.size() - 1; i > master_plan_idx; i--) {
-        // FIXME: Do we need a finalize here, or is the rule that
-        // "PrepareForStop"
-        // for the plan leaves it in a state that it is safe to pop the plan
-        // with no more notice?
-        DiscardPlan();
-      }
-
-      // Now discard the master plan itself.
-      // The bottom-most plan never gets discarded.  "OkayToDiscard" for it
-      // means discard it's dependent plans, but not it...
-      if (master_plan_idx > 0) {
-        DiscardPlan();
-      }
-    } else {
-      // If the master plan doesn't want to get discarded, then we're done.
-      break;
-    }
-  }
-}
-
-bool Thread::PlanIsBasePlan(ThreadPlan *plan_ptr) {
-  if (plan_ptr->IsBasePlan())
-    return true;
-  else if (m_plan_stack.size() == 0)
-    return false;
-  else
-    return m_plan_stack[0].get() == plan_ptr;
+  GetPlans().DiscardConsultingMasterPlans();
 }
 
 Status Thread::UnwindInnermostExpression() {
   Status error;
-  int stack_size = m_plan_stack.size();
-
-  // If the input plan is nullptr, discard all plans.  Otherwise make sure this
-  // plan is in the stack, and if so discard up to and including it.
-
-  for (int i = stack_size - 1; i > 0; i--) {
-    if (m_plan_stack[i]->GetKind() == ThreadPlan::eKindCallFunction) {
-      DiscardThreadPlansUpToPlan(m_plan_stack[i].get());
-      return error;
-    }
-  }
-  error.SetErrorString("No expressions currently active on this thread");
+  ThreadPlan *innermost_expr_plan = GetPlans().GetInnermostExpression();
+  if (!innermost_expr_plan) {
+    error.SetErrorString("No expressions currently active on this thread");
+    return error;
+  }  
+  DiscardThreadPlansUpToPlan(innermost_expr_plan);
   return error;
 }
 
@@ -1559,40 +1372,12 @@ lldb::ThreadPlanSP Thread::QueueThreadPlanForStepScripted(
 
 uint32_t Thread::GetIndexID() const { return m_index_id; }
 
-static void PrintPlanElement(Stream *s, const ThreadPlanSP &plan,
-                             lldb::DescriptionLevel desc_level,
-                             int32_t elem_idx) {
-  s->IndentMore();
-  s->Indent();
-  s->Printf("Element %d: ", elem_idx);
-  plan->GetDescription(s, desc_level);
-  s->EOL();
-  s->IndentLess();
-}
-
-static void PrintPlanStack(Stream *s,
-                           const std::vector<lldb::ThreadPlanSP> &plan_stack,
-                           lldb::DescriptionLevel desc_level,
-                           bool include_internal) {
-  int32_t print_idx = 0;
-  for (ThreadPlanSP plan_sp : plan_stack) {
-    if (include_internal || !plan_sp->GetPrivate()) {
-      PrintPlanElement(s, plan_sp, desc_level, print_idx++);
-    }
-  }
-}
-
 void Thread::DumpThreadPlans(Stream *s, lldb::DescriptionLevel desc_level,
                              bool include_internal,
                              bool ignore_boring_threads) const {
-  uint32_t stack_size;
-
   if (ignore_boring_threads) {
-    uint32_t stack_size = m_plan_stack.size();
-    uint32_t completed_stack_size = m_completed_plan_stack.size();
-    uint32_t discarded_stack_size = m_discarded_plan_stack.size();
-    if (stack_size == 1 && completed_stack_size == 0 &&
-        discarded_stack_size == 0) {
+    if (!GetPlans().AnyPlans() && !GetPlans().AnyCompletedPlans()
+        && !GetPlans().AnyDiscardedPlans()) {
       s->Printf("thread #%u: tid = 0x%4.4" PRIx64 "\n", GetIndexID(), GetID());
       s->IndentMore();
       s->Indent();
@@ -1601,29 +1386,10 @@ void Thread::DumpThreadPlans(Stream *s, lldb::DescriptionLevel desc_level,
       return;
     }
   }
-
+  
   s->Indent();
   s->Printf("thread #%u: tid = 0x%4.4" PRIx64 ":\n", GetIndexID(), GetID());
-  s->IndentMore();
-  s->Indent();
-  s->Printf("Active plan stack:\n");
-  PrintPlanStack(s, m_plan_stack, desc_level, include_internal);
-
-  stack_size = m_completed_plan_stack.size();
-  if (stack_size > 0) {
-    s->Indent();
-    s->Printf("Completed Plan Stack:\n");
-    PrintPlanStack(s, m_completed_plan_stack, desc_level, include_internal);
-  }
-
-  stack_size = m_discarded_plan_stack.size();
-  if (stack_size > 0) {
-    s->Indent();
-    s->Printf("Discarded Plan Stack:\n");
-    PrintPlanStack(s, m_discarded_plan_stack, desc_level, include_internal);
-  }
-
-  s->IndentLess();
+  GetPlans().DumpThreadPlans(s, desc_level, include_internal);
 }
 
 TargetSP Thread::CalculateTarget() {
index 8d90e48..327ff97 100644 (file)
@@ -726,8 +726,10 @@ void ThreadList::Update(ThreadList &rhs) {
           break;
         }
       }
-      if (!thread_is_alive)
+      if (!thread_is_alive) {
         (*rhs_pos)->DestroyThread();
+        m_process->RemoveThreadPlansForTID((*rhs_pos)->GetID());
+      }
     }
   }
 }
index 8253023..d0da8e1 100644 (file)
@@ -34,6 +34,10 @@ ThreadPlan::ThreadPlan(ThreadPlanKind kind, const char *name, Thread &thread,
 // Destructor
 ThreadPlan::~ThreadPlan() = default;
 
+Target &ThreadPlan::GetTarget() { return m_process.GetTarget(); }
+
+const Target &ThreadPlan::GetTarget() const { return m_process.GetTarget(); }
+
 Thread &ThreadPlan::GetThread() {
   if (m_thread)
     return *m_thread;
diff --git a/lldb/source/Target/ThreadPlanStack.cpp b/lldb/source/Target/ThreadPlanStack.cpp
new file mode 100644 (file)
index 0000000..bd11718
--- /dev/null
@@ -0,0 +1,370 @@
+//===-- ThreadPlanStack.cpp -------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/ThreadPlanStack.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Target/ThreadPlan.h"
+#include "lldb/Utility/Log.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+static void PrintPlanElement(Stream *s, const ThreadPlanSP &plan,
+                             lldb::DescriptionLevel desc_level,
+                             int32_t elem_idx) {
+  s->IndentMore();
+  s->Indent();
+  s->Printf("Element %d: ", elem_idx);
+  plan->GetDescription(s, desc_level);
+  s->EOL();
+  s->IndentLess();
+}
+
+void ThreadPlanStack::DumpThreadPlans(Stream *s,
+                                      lldb::DescriptionLevel desc_level,
+                                      bool include_internal) const {
+
+  uint32_t stack_size;
+
+  s->IndentMore();
+  s->Indent();
+  s->Printf("Active plan stack:\n");
+  int32_t print_idx = 0;
+  for (auto plan : m_plans) {
+    PrintPlanElement(s, plan, desc_level, print_idx++);
+  }
+
+  if (AnyCompletedPlans()) {
+    print_idx = 0;
+    s->Indent();
+    s->Printf("Completed Plan Stack:\n");
+    for (auto plan : m_completed_plans)
+      PrintPlanElement(s, plan, desc_level, print_idx++);
+  }
+
+  if (AnyDiscardedPlans()) {
+    print_idx = 0;
+    s->Indent();
+    s->Printf("Discarded Plan Stack:\n");
+    for (auto plan : m_discarded_plans)
+      PrintPlanElement(s, plan, desc_level, print_idx++);
+  }
+
+  s->IndentLess();
+}
+
+size_t ThreadPlanStack::CheckpointCompletedPlans() {
+  m_completed_plan_checkpoint++;
+  m_completed_plan_store.insert(
+      std::make_pair(m_completed_plan_checkpoint, m_completed_plans));
+  return m_completed_plan_checkpoint;
+}
+
+void ThreadPlanStack::RestoreCompletedPlanCheckpoint(size_t checkpoint) {
+  auto result = m_completed_plan_store.find(checkpoint);
+  assert(result != m_completed_plan_store.end() &&
+         "Asked for a checkpoint that didn't exist");
+  m_completed_plans.swap((*result).second);
+  m_completed_plan_store.erase(result);
+}
+
+void ThreadPlanStack::DiscardCompletedPlanCheckpoint(size_t checkpoint) {
+  m_completed_plan_store.erase(checkpoint);
+}
+
+void ThreadPlanStack::ThreadDestroyed(Thread *thread) {
+  // Tell the plan stacks that this thread is going away:
+  for (ThreadPlanSP plan : m_plans)
+    plan->ThreadDestroyed();
+
+  for (ThreadPlanSP plan : m_discarded_plans)
+    plan->ThreadDestroyed();
+
+  for (ThreadPlanSP plan : m_completed_plans)
+    plan->ThreadDestroyed();
+
+  // Now clear the current plan stacks:
+  m_plans.clear();
+  m_discarded_plans.clear();
+  m_completed_plans.clear();
+
+  // Push a ThreadPlanNull on the plan stack.  That way we can continue
+  // assuming that the plan stack is never empty, but if somebody errantly asks
+  // questions of a destroyed thread without checking first whether it is
+  // destroyed, they won't crash.
+  if (thread != nullptr) {
+    lldb::ThreadPlanSP null_plan_sp(new ThreadPlanNull(*thread));
+    m_plans.push_back(null_plan_sp);
+  }
+}
+
+void ThreadPlanStack::EnableTracer(bool value, bool single_stepping) {
+  for (ThreadPlanSP plan : m_plans) {
+    if (plan->GetThreadPlanTracer()) {
+      plan->GetThreadPlanTracer()->EnableTracing(value);
+      plan->GetThreadPlanTracer()->EnableSingleStep(single_stepping);
+    }
+  }
+}
+
+void ThreadPlanStack::SetTracer(lldb::ThreadPlanTracerSP &tracer_sp) {
+  for (ThreadPlanSP plan : m_plans)
+    plan->SetThreadPlanTracer(tracer_sp);
+}
+
+void ThreadPlanStack::PushPlan(lldb::ThreadPlanSP new_plan_sp) {
+  // If the thread plan doesn't already have a tracer, give it its parent's
+  // tracer:
+  // The first plan has to be a base plan:
+  assert(m_plans.size() > 0 ||
+         new_plan_sp->IsBasePlan() && "Zeroth plan must be a base plan");
+
+  if (!new_plan_sp->GetThreadPlanTracer()) {
+    assert(!m_plans.empty());
+    new_plan_sp->SetThreadPlanTracer(m_plans.back()->GetThreadPlanTracer());
+  }
+  m_plans.push_back(new_plan_sp);
+  new_plan_sp->DidPush();
+}
+
+lldb::ThreadPlanSP ThreadPlanStack::PopPlan() {
+  assert(m_plans.size() > 1 && "Can't pop the base thread plan");
+
+  lldb::ThreadPlanSP &plan_sp = m_plans.back();
+  m_completed_plans.push_back(plan_sp);
+  plan_sp->WillPop();
+  m_plans.pop_back();
+  return plan_sp;
+}
+
+lldb::ThreadPlanSP ThreadPlanStack::DiscardPlan() {
+  assert(m_plans.size() > 1 && "Can't discard the base thread plan");
+
+  lldb::ThreadPlanSP &plan_sp = m_plans.back();
+  m_discarded_plans.push_back(plan_sp);
+  plan_sp->WillPop();
+  m_plans.pop_back();
+  return plan_sp;
+}
+
+// If the input plan is nullptr, discard all plans.  Otherwise make sure this
+// plan is in the stack, and if so discard up to and including it.
+void ThreadPlanStack::DiscardPlansUpToPlan(ThreadPlan *up_to_plan_ptr) {
+  int stack_size = m_plans.size();
+
+  if (up_to_plan_ptr == nullptr) {
+    for (int i = stack_size - 1; i > 0; i--)
+      DiscardPlan();
+    return;
+  }
+
+  bool found_it = false;
+  for (int i = stack_size - 1; i > 0; i--) {
+    if (m_plans[i].get() == up_to_plan_ptr) {
+      found_it = true;
+      break;
+    }
+  }
+
+  if (found_it) {
+    bool last_one = false;
+    for (int i = stack_size - 1; i > 0 && !last_one; i--) {
+      if (GetCurrentPlan().get() == up_to_plan_ptr)
+        last_one = true;
+      DiscardPlan();
+    }
+  }
+}
+
+void ThreadPlanStack::DiscardAllPlans() {
+  int stack_size = m_plans.size();
+  for (int i = stack_size - 1; i > 0; i--) {
+    DiscardPlan();
+  }
+  return;
+}
+
+void ThreadPlanStack::DiscardConsultingMasterPlans() {
+  while (true) {
+    int master_plan_idx;
+    bool discard = true;
+
+    // Find the first master plan, see if it wants discarding, and if yes
+    // discard up to it.
+    for (master_plan_idx = m_plans.size() - 1; master_plan_idx >= 0;
+         master_plan_idx--) {
+      if (m_plans[master_plan_idx]->IsMasterPlan()) {
+        discard = m_plans[master_plan_idx]->OkayToDiscard();
+        break;
+      }
+    }
+
+    // If the master plan doesn't want to get discarded, then we're done.
+    if (!discard)
+      return;
+
+    // First pop all the dependent plans:
+    for (int i = m_plans.size() - 1; i > master_plan_idx; i--) {
+      DiscardPlan();
+    }
+
+    // Now discard the master plan itself.
+    // The bottom-most plan never gets discarded.  "OkayToDiscard" for it
+    // means discard it's dependent plans, but not it...
+    if (master_plan_idx > 0) {
+      DiscardPlan();
+    }
+  }
+}
+
+lldb::ThreadPlanSP ThreadPlanStack::GetCurrentPlan() const {
+  assert(m_plans.size() != 0 && "There will always be a base plan.");
+  return m_plans.back();
+}
+
+lldb::ThreadPlanSP ThreadPlanStack::GetCompletedPlan(bool skip_private) const {
+  if (m_completed_plans.empty())
+    return {};
+
+  if (!skip_private)
+    return m_completed_plans.back();
+
+  for (int i = m_completed_plans.size() - 1; i >= 0; i--) {
+    lldb::ThreadPlanSP completed_plan_sp;
+    completed_plan_sp = m_completed_plans[i];
+    if (!completed_plan_sp->GetPrivate())
+      return completed_plan_sp;
+  }
+  return {};
+}
+
+lldb::ThreadPlanSP ThreadPlanStack::GetPlanByIndex(uint32_t plan_idx,
+                                                   bool skip_private) const {
+  uint32_t idx = 0;
+  ThreadPlan *up_to_plan_ptr = nullptr;
+
+  for (lldb::ThreadPlanSP plan_sp : m_plans) {
+    if (skip_private && plan_sp->GetPrivate())
+      continue;
+    if (idx == plan_idx)
+      return plan_sp;
+    idx++;
+  }
+  return {};
+}
+
+lldb::ValueObjectSP ThreadPlanStack::GetReturnValueObject() const {
+  if (m_completed_plans.empty())
+    return {};
+
+  for (int i = m_completed_plans.size() - 1; i >= 0; i--) {
+    lldb::ValueObjectSP return_valobj_sp;
+    return_valobj_sp = m_completed_plans[i]->GetReturnValueObject();
+    if (return_valobj_sp)
+      return return_valobj_sp;
+  }
+  return {};
+}
+
+lldb::ExpressionVariableSP ThreadPlanStack::GetExpressionVariable() const {
+  if (m_completed_plans.empty())
+    return {};
+
+  for (int i = m_completed_plans.size() - 1; i >= 0; i--) {
+    lldb::ExpressionVariableSP expression_variable_sp;
+    expression_variable_sp = m_completed_plans[i]->GetExpressionVariable();
+    if (expression_variable_sp)
+      return expression_variable_sp;
+  }
+  return {};
+}
+bool ThreadPlanStack::AnyPlans() const {
+  // There is always a base plan...
+  return m_plans.size() > 1;
+}
+
+bool ThreadPlanStack::AnyCompletedPlans() const {
+  return !m_completed_plans.empty();
+}
+
+bool ThreadPlanStack::AnyDiscardedPlans() const {
+  return !m_discarded_plans.empty();
+}
+
+bool ThreadPlanStack::IsPlanDone(ThreadPlan *in_plan) const {
+  for (auto plan : m_completed_plans) {
+    if (plan.get() == in_plan)
+      return true;
+  }
+  return false;
+}
+
+bool ThreadPlanStack::WasPlanDiscarded(ThreadPlan *in_plan) const {
+  for (auto plan : m_discarded_plans) {
+    if (plan.get() == in_plan)
+      return true;
+  }
+  return false;
+}
+
+ThreadPlan *ThreadPlanStack::GetPreviousPlan(ThreadPlan *current_plan) const {
+  if (current_plan == nullptr)
+    return nullptr;
+
+  // Look first in the completed plans, if the plan is here and there is
+  // a completed plan above it, return that.
+  int stack_size = m_completed_plans.size();
+  for (int i = stack_size - 1; i > 0; i--) {
+    if (current_plan == m_completed_plans[i].get())
+      return m_completed_plans[i - 1].get();
+  }
+
+  // If this is the first completed plan, the previous one is the
+  // bottom of the regular plan stack.
+  if (stack_size > 0 && m_completed_plans[0].get() == current_plan) {
+    return GetCurrentPlan().get();
+  }
+
+  // Otherwise look for it in the regular plans.
+  stack_size = m_plans.size();
+  for (int i = stack_size - 1; i > 0; i--) {
+    if (current_plan == m_plans[i].get())
+      return m_plans[i - 1].get();
+  }
+  return nullptr;
+}
+
+ThreadPlan *ThreadPlanStack::GetInnermostExpression() const {
+  int stack_size = m_plans.size();
+
+  for (int i = stack_size - 1; i > 0; i--) {
+    if (m_plans[i]->GetKind() == ThreadPlan::eKindCallFunction)
+      return m_plans[i].get();
+  }
+  return nullptr;
+}
+
+void ThreadPlanStack::WillResume() {
+  m_completed_plans.clear();
+  m_discarded_plans.clear();
+}
+
+const ThreadPlanStack::PlanStack &
+ThreadPlanStack::GetStackOfKind(ThreadPlanStack::StackKind kind) const {
+  switch (kind) {
+  case ePlans:
+    return m_plans;
+  case eCompletedPlans:
+    return m_completed_plans;
+  case eDiscardedPlans:
+    return m_discarded_plans;
+  }
+  llvm_unreachable("Invalid StackKind value");
+}