[Lldb-commits] [lldb] [lldb-dap] Send an Invalidated event on thread stack change. (PR #163976)

Ebuka Ezike via lldb-commits lldb-commits at lists.llvm.org
Mon Oct 20 13:34:43 PDT 2025


https://github.com/da-viper updated https://github.com/llvm/llvm-project/pull/163976

>From c20a05867dc386698c28d6244b88959686b29c48 Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Fri, 17 Oct 2025 11:39:20 +0100
Subject: [PATCH 1/3] [lldb-dap] Send an Invalidated event on thread stack
 change.

When the user send `thread return <expr>` command this changes
the stack length. but the UI does not update.
Send stack invalidated event to update the stack
---
 lldb/tools/lldb-dap/DAP.cpp                   | 19 +++++++++++++++++++
 lldb/tools/lldb-dap/DAP.h                     |  1 +
 lldb/tools/lldb-dap/EventHelper.cpp           |  8 +++++++-
 lldb/tools/lldb-dap/EventHelper.h             |  5 ++++-
 .../lldb-dap/Protocol/ProtocolEvents.cpp      |  2 +-
 5 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f76656e98ca01..61226cceb6db0 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -1368,6 +1368,12 @@ void DAP::EventThread() {
   broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
   debugger.GetBroadcaster().AddListener(
       listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning);
+
+  // listen for thread events.
+  listener.StartListeningForEventClass(
+      debugger, lldb::SBThread::GetBroadcasterClassName(),
+      lldb::SBThread::eBroadcastBitStackChanged);
+
   bool done = false;
   while (!done) {
     if (listener.WaitForEvent(1, event)) {
@@ -1503,6 +1509,9 @@ void DAP::EventThread() {
             SendJSON(llvm::json::Value(std::move(bp_event)));
           }
         }
+
+      } else if (lldb::SBThread::EventIsThreadEvent(event)) {
+        HandleThreadEvent(event);
       } else if (event_mask & lldb::eBroadcastBitError ||
                  event_mask & lldb::eBroadcastBitWarning) {
         lldb::SBStructuredData data =
@@ -1522,6 +1531,16 @@ void DAP::EventThread() {
   }
 }
 
+void DAP::HandleThreadEvent(const lldb::SBEvent &event) {
+  uint32_t event_type = event.GetType();
+
+  if (event_type & lldb::SBThread::eBroadcastBitStackChanged) {
+    const lldb::SBThread evt_thread = lldb::SBThread::GetThreadFromEvent(event);
+    SendInvalidatedEvent(*this, {InvalidatedEventBody::eAreaStacks},
+                         evt_thread.GetThreadID());
+  }
+}
+
 std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints(
     const protocol::Source &source,
     const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index a90ddf59671ee..bf2c3f146a396 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -460,6 +460,7 @@ struct DAP final : public DAPTransport::MessageHandler {
   /// Event threads.
   /// @{
   void EventThread();
+  void HandleThreadEvent(const lldb::SBEvent &event);
   void ProgressEventThread();
 
   std::thread event_thread;
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index 2b9ed229405a8..04ed8a052bd76 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -15,6 +15,7 @@
 #include "Protocol/ProtocolRequests.h"
 #include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBFileSpec.h"
+#include "lldb/lldb-defines.h"
 #include "llvm/Support/Error.h"
 #include <utility>
 
@@ -276,11 +277,16 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
 }
 
 void SendInvalidatedEvent(
-    DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas) {
+    DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas,
+    lldb::tid_t tid) {
   if (!dap.clientFeatures.contains(protocol::eClientFeatureInvalidatedEvent))
     return;
   protocol::InvalidatedEventBody body;
   body.areas = areas;
+
+  if (tid != LLDB_INVALID_THREAD_ID)
+    body.threadId = tid;
+
   dap.Send(protocol::Event{"invalidated", std::move(body)});
 }
 
diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h
index 48eb5af6bd0b9..be783d032a5ae 100644
--- a/lldb/tools/lldb-dap/EventHelper.h
+++ b/lldb/tools/lldb-dap/EventHelper.h
@@ -11,6 +11,8 @@
 
 #include "DAPForward.h"
 #include "Protocol/ProtocolEvents.h"
+#include "lldb/lldb-defines.h"
+#include "lldb/lldb-types.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/Support/Error.h"
 
@@ -35,7 +37,8 @@ void SendContinuedEvent(DAP &dap);
 void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process);
 
 void SendInvalidatedEvent(
-    DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas);
+    DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas,
+    lldb::tid_t tid = LLDB_INVALID_THREAD_ID);
 
 void SendMemoryEvent(DAP &dap, lldb::SBValue variable);
 
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
index b896eca817be6..df6be06637a13 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp
@@ -51,7 +51,7 @@ llvm::json::Value toJSON(const InvalidatedEventBody::Area &IEBA) {
 llvm::json::Value toJSON(const InvalidatedEventBody &IEB) {
   json::Object Result{{"areas", IEB.areas}};
   if (IEB.threadId)
-    Result.insert({"threadID", IEB.threadId});
+    Result.insert({"threadId", IEB.threadId});
   if (IEB.stackFrameId)
     Result.insert({"stackFrameId", IEB.stackFrameId});
   return Result;

>From 6e9df0544dbfcd32302d930c196ce8dca787546f Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Fri, 17 Oct 2025 16:00:36 +0100
Subject: [PATCH 2/3] [lldb-dap] add test case

---
 .../tools/lldb-dap/invalidated-event/Makefile |  3 +
 .../TestDAP_invalidatedEvent.py               | 55 +++++++++++++++++++
 .../tools/lldb-dap/invalidated-event/main.cpp |  9 +++
 .../tools/lldb-dap/invalidated-event/other.h  | 10 ++++
 lldb/unittests/DAP/ProtocolTypesTest.cpp      | 19 ++++---
 5 files changed, 88 insertions(+), 8 deletions(-)
 create mode 100644 lldb/test/API/tools/lldb-dap/invalidated-event/Makefile
 create mode 100644 lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py
 create mode 100644 lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp
 create mode 100644 lldb/test/API/tools/lldb-dap/invalidated-event/other.h

diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/Makefile b/lldb/test/API/tools/lldb-dap/invalidated-event/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py b/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py
new file mode 100644
index 0000000000000..aef63ffe81060
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py
@@ -0,0 +1,55 @@
+"""
+Test lldb-dap recieves invalidated-events when the area such as
+stack, variables, threads has changes but the client does not
+know about it.
+"""
+
+import lldbdap_testcase
+from lldbsuite.test.lldbtest import line_number
+from dap_server import Event
+
+
+class TestDAP_invalidatedEvent(lldbdap_testcase.DAPTestCaseBase):
+    def verify_top_frame_name(self, frame_name: str):
+        all_frames = self.get_stackFrames()
+        self.assertGreaterEqual(len(all_frames), 1, "Expected at least one frame.")
+        top_frame_name = all_frames[0]["name"]
+        self.assertRegex(top_frame_name, f"{frame_name}.*")
+
+    def test_invalidated_stack_area_event(self):
+        """
+        Test an invalidated event for the stack area.
+        The event is sent when the command `thread return <expr>` is sent by the user.
+        """
+        other_source = "other.h"
+        return_bp_line = line_number(other_source, "// thread return breakpoint")
+
+        program = self.getBuildArtifact("a.out")
+        self.build_and_launch(program)
+        self.set_source_breakpoints(other_source, [return_bp_line])
+        self.continue_to_next_stop()
+
+        self.verify_top_frame_name("add")
+        thread_id = self.dap_server.get_thread_id()
+        self.assertIsNotNone(thread_id, "Exepected a thread id.")
+
+        # run thread return
+        thread_command = "thread return 20"
+        eval_resp = self.dap_server.request_evaluate(thread_command, context="repl")
+        self.assertTrue(eval_resp["success"], f"Failed to evaluate `{thread_command}`.")
+
+        # wait for the invalidated stack event.
+        stack_event = self.dap_server.wait_for_event(["invalidated"])
+        self.assertIsNotNone(stack_event, "Expected an invalidated event.")
+        event_body: Event = stack_event["body"]
+        self.assertIn("stacks", event_body["areas"])
+        self.assertIn("threadId", event_body.keys())
+        self.assertEqual(
+            thread_id,
+            event_body["threadId"],
+            f"Expected the  event from thread {thread_id}.",
+        )
+
+        # confirm we are back at the main frame.
+        self.verify_top_frame_name("main")
+        self.continue_to_exit()
diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp b/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp
new file mode 100644
index 0000000000000..9fed5cf90f3f1
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp
@@ -0,0 +1,9 @@
+#include "other.h"
+
+int main() {
+  int first = 5;
+  int second = 10;
+  const int result = add(first, second);
+
+  return 0;
+}
\ No newline at end of file
diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/other.h b/lldb/test/API/tools/lldb-dap/invalidated-event/other.h
new file mode 100644
index 0000000000000..c3e242676612b
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/other.h
@@ -0,0 +1,10 @@
+#ifndef OTHER_H
+#define OTHER_H
+
+int add(int a, int b) {
+  int first = a;
+  int second = b; // thread return breakpoint
+  int result = first + second;
+  return result;
+}
+#endif // OTHER_H
\ No newline at end of file
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index a5ae856a185b7..8170abdd25bc6 100644
--- a/lldb/unittests/DAP/ProtocolTypesTest.cpp
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -1079,14 +1079,17 @@ TEST(ProtocolTypesTest, InvalidatedEventBody) {
   body.areas = {InvalidatedEventBody::eAreaStacks,
                 InvalidatedEventBody::eAreaThreads};
   body.stackFrameId = 1;
-  StringRef json = R"({
-  "areas": [
-    "stacks",
-    "threads"
-  ],
-  "stackFrameId": 1
-})";
-  EXPECT_EQ(json, pp(body));
+  body.threadId = 20;
+  Expected<json::Value> expected = json::parse(R"({
+    "areas": [
+      "stacks",
+      "threads"
+    ],
+    "stackFrameId": 1,
+    "threadId": 20
+    })");
+  ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
+  EXPECT_EQ(pp(*expected), pp(body));
 }
 
 TEST(ProtocolTypesTest, MemoryEventBody) {

>From bf6c3286105796ce0396b9667469d13a3bc0b8cf Mon Sep 17 00:00:00 2001
From: Ebuka Ezike <yerimyah1 at gmail.com>
Date: Sun, 19 Oct 2025 18:56:40 +0100
Subject: [PATCH 3/3] [lldb-dap] add review changes

---
 .../lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py      | 2 +-
 lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp         | 2 +-
 lldb/test/API/tools/lldb-dap/invalidated-event/other.h          | 2 +-
 lldb/tools/lldb-dap/EventHelper.cpp                             | 1 -
 4 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py b/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py
index aef63ffe81060..8ba56b0bb27ca 100644
--- a/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py
@@ -47,7 +47,7 @@ def test_invalidated_stack_area_event(self):
         self.assertEqual(
             thread_id,
             event_body["threadId"],
-            f"Expected the  event from thread {thread_id}.",
+            f"Expected the event from thread {thread_id}.",
         )
 
         # confirm we are back at the main frame.
diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp b/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp
index 9fed5cf90f3f1..c82f9532b7517 100644
--- a/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp
@@ -6,4 +6,4 @@ int main() {
   const int result = add(first, second);
 
   return 0;
-}
\ No newline at end of file
+}
diff --git a/lldb/test/API/tools/lldb-dap/invalidated-event/other.h b/lldb/test/API/tools/lldb-dap/invalidated-event/other.h
index c3e242676612b..856db446d7b5a 100644
--- a/lldb/test/API/tools/lldb-dap/invalidated-event/other.h
+++ b/lldb/test/API/tools/lldb-dap/invalidated-event/other.h
@@ -7,4 +7,4 @@ int add(int a, int b) {
   int result = first + second;
   return result;
 }
-#endif // OTHER_H
\ No newline at end of file
+#endif // OTHER_H
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index 04ed8a052bd76..3042d3293b482 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -15,7 +15,6 @@
 #include "Protocol/ProtocolRequests.h"
 #include "Protocol/ProtocolTypes.h"
 #include "lldb/API/SBFileSpec.h"
-#include "lldb/lldb-defines.h"
 #include "llvm/Support/Error.h"
 #include <utility>
 



More information about the lldb-commits mailing list