[Lldb-commits] [lldb] 943782b - [lldb] Broadcast `eBroadcastBitStackChanged` when frame providers change (#171482)

via lldb-commits lldb-commits at lists.llvm.org
Thu Feb 5 12:00:28 PST 2026


Author: Adrian Vogelsgesang
Date: 2026-02-05T21:00:23+01:00
New Revision: 943782be5aec5db854065145dd73a618c3a775be

URL: https://github.com/llvm/llvm-project/commit/943782be5aec5db854065145dd73a618c3a775be
DIFF: https://github.com/llvm/llvm-project/commit/943782be5aec5db854065145dd73a618c3a775be.diff

LOG: [lldb] Broadcast `eBroadcastBitStackChanged` when frame providers change (#171482)

We want to reload the call stack whenever the frame providers are
updated. To do so, we now emit a `eBroadcastBitStackChanged` on all
threads whenever any changes to the frame providers take place.

I found this very useful while iterating on a frame provider in
lldb-dap. So far, the new frame provider only took effect after
continuing execution. Now the backtrace in VS-Code gets refreshed
immediately upon running `target frame-provider add`.

Added: 
    

Modified: 
    lldb/include/lldb/Target/Target.h
    lldb/source/Target/Target.cpp
    lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index f781c4dabdd9f..4f5b022765f9e 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -800,6 +800,12 @@ class Target : public std::enable_shared_from_this<Target>,
   const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
   GetScriptedFrameProviderDescriptors() const;
 
+protected:
+  /// Invalidate all potentially cached frame providers for all threads
+  /// and trigger a stack changed event for all threads.
+  void InvalidateThreadFrameProviders();
+
+public:
   // This part handles the breakpoints.
 
   BreakpointList &GetBreakpointList(bool internal = false);

diff  --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index d9e72cd5453e4..787e66bfaf7a8 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3726,47 +3726,45 @@ llvm::Expected<uint32_t> Target::AddScriptedFrameProviderDescriptor(
   if (!descriptor.IsValid())
     return llvm::createStringError("invalid frame provider descriptor");
 
+  uint32_t descriptor_id = descriptor.GetID();
+
   llvm::StringRef name = descriptor.GetName();
   if (name.empty())
     return llvm::createStringError(
         "frame provider descriptor has no class name");
 
-  std::lock_guard<std::recursive_mutex> guard(
-      m_frame_provider_descriptors_mutex);
-
-  uint32_t descriptor_id = descriptor.GetID();
-  m_frame_provider_descriptors[descriptor_id] = descriptor;
+  {
+    std::unique_lock<std::recursive_mutex> guard(
+        m_frame_provider_descriptors_mutex);
+    m_frame_provider_descriptors[descriptor_id] = descriptor;
+  }
 
-  // Clear frame providers on existing threads so they reload with new config.
-  if (ProcessSP process_sp = GetProcessSP())
-    for (ThreadSP thread_sp : process_sp->Threads())
-      thread_sp->ClearScriptedFrameProvider();
+  InvalidateThreadFrameProviders();
 
   return descriptor_id;
 }
 
 bool Target::RemoveScriptedFrameProviderDescriptor(uint32_t id) {
-  std::lock_guard<std::recursive_mutex> guard(
-      m_frame_provider_descriptors_mutex);
-  bool removed = m_frame_provider_descriptors.erase(id);
+  bool removed = false;
+  {
+    std::lock_guard<std::recursive_mutex> guard(
+        m_frame_provider_descriptors_mutex);
+    removed = m_frame_provider_descriptors.erase(id);
+  }
 
   if (removed)
-    if (ProcessSP process_sp = GetProcessSP())
-      for (ThreadSP thread_sp : process_sp->Threads())
-        thread_sp->ClearScriptedFrameProvider();
-
+    InvalidateThreadFrameProviders();
   return removed;
 }
 
 void Target::ClearScriptedFrameProviderDescriptors() {
-  std::lock_guard<std::recursive_mutex> guard(
-      m_frame_provider_descriptors_mutex);
-
-  m_frame_provider_descriptors.clear();
+  {
+    std::lock_guard<std::recursive_mutex> guard(
+        m_frame_provider_descriptors_mutex);
+    m_frame_provider_descriptors.clear();
+  }
 
-  if (ProcessSP process_sp = GetProcessSP())
-    for (ThreadSP thread_sp : process_sp->Threads())
-      thread_sp->ClearScriptedFrameProvider();
+  InvalidateThreadFrameProviders();
 }
 
 const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
@@ -3776,6 +3774,21 @@ Target::GetScriptedFrameProviderDescriptors() const {
   return m_frame_provider_descriptors;
 }
 
+void Target::InvalidateThreadFrameProviders() {
+  ProcessSP process_sp = GetProcessSP();
+  if (!process_sp)
+    return;
+  for (ThreadSP thread_sp : process_sp->Threads()) {
+    // Clear frame providers on existing threads so they reload with new config.
+    thread_sp->ClearScriptedFrameProvider();
+    // Notify threads that the stack traces might have changed.
+    if (thread_sp->EventTypeHasListeners(Thread::eBroadcastBitStackChanged)) {
+      auto data_sp = std::make_shared<Thread::ThreadEventData>(thread_sp);
+      thread_sp->BroadcastEvent(Thread::eBroadcastBitStackChanged, data_sp);
+    }
+  }
+}
+
 void Target::FinalizeFileActions(ProcessLaunchInfo &info) {
   Log *log = GetLog(LLDBLog::Process);
 

diff  --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 8397d60eedbb5..8c2d8f0d5ad52 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -1110,3 +1110,80 @@ def test_provider_lifecycle_with_frame_validity(self):
             _ = saved_frames[1].IsValid()
         except Exception as e:
             self.fail(f"Accessing invalidated frames should not crash: {e}")
+
+    def test_event_broadcasting(self):
+        """Test that adding/removing frame providers broadcasts eBroadcastBitStackChanged."""
+        self.build()
+
+        listener = lldb.SBListener("stack_changed_listener")
+        listener.StartListeningForEventClass(
+            self.dbg,
+            lldb.SBThread.GetBroadcasterClassName(),
+            lldb.SBThread.eBroadcastBitStackChanged,
+        )
+
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
+        )
+
+        expected_thread_ids = {
+            process.GetThreadAtIndex(i).GetIndexID()
+            for i in range(process.GetNumThreads())
+        }
+
+        def collect_stack_changed_thread_ids(count):
+            event = lldb.SBEvent()
+            thread_ids = set()
+            for _ in range(count):
+                if not listener.WaitForEvent(5, event):
+                    break
+                self.assertEqual(
+                    event.GetType(),
+                    lldb.SBThread.eBroadcastBitStackChanged,
+                    "Event should be stack changed",
+                )
+                thread_ids.add(lldb.SBThread.GetThreadFromEvent(event).GetIndexID())
+            return thread_ids
+
+        # Import the test frame provider.
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        # 1. Test registration.
+        error = lldb.SBError()
+        provider_id = target.RegisterScriptedFrameProvider(
+            "test_frame_providers.ReplaceFrameProvider",
+            lldb.SBStructuredData(),
+            error,
+        )
+        self.assertSuccess(error, f"Failed to register provider: {error}")
+        self.assertEqual(
+            collect_stack_changed_thread_ids(len(expected_thread_ids)),
+            expected_thread_ids,
+            "All threads should broadcast eBroadcastBitStackChanged on registration",
+        )
+
+        # 2. Test removal.
+        result = target.RemoveScriptedFrameProvider(provider_id)
+        self.assertSuccess(result, f"Failed to remove provider: {result}")
+        self.assertEqual(
+            collect_stack_changed_thread_ids(len(expected_thread_ids)),
+            expected_thread_ids,
+            "All threads should broadcast eBroadcastBitStackChanged on removal",
+        )
+
+        # 3. Test clear.
+        target.RegisterScriptedFrameProvider(
+            "test_frame_providers.ReplaceFrameProvider",
+            lldb.SBStructuredData(),
+            error,
+        )
+        # Consume registration
+        collect_stack_changed_thread_ids(len(expected_thread_ids))
+
+        self.runCmd("target frame-provider clear")
+        self.assertEqual(
+            collect_stack_changed_thread_ids(len(expected_thread_ids)),
+            expected_thread_ids,
+            "All threads should broadcast eBroadcastBitStackChanged on clear",
+        )
\ No newline at end of file


        


More information about the lldb-commits mailing list