aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEbuka Ezike <yerimyah1@gmail.com>2025-10-20 21:54:13 +0100
committerGitHub <noreply@github.com>2025-10-20 21:54:13 +0100
commitcd67ca2f11f79d2ce08807682b571ca04dab0996 (patch)
tree3a0771b2158e50bc0f327bcf571512afd37117ed
parent670fb3e7683c1fc83bcfff04bb15488617ff2a34 (diff)
downloadllvm-cd67ca2f11f79d2ce08807682b571ca04dab0996.tar.gz
llvm-cd67ca2f11f79d2ce08807682b571ca04dab0996.tar.bz2
llvm-cd67ca2f11f79d2ce08807682b571ca04dab0996.zip
[lldb-dap] Send an Invalidated event on thread stack change. (#163976)
When the user send `thread return <expr>` command this changes the stack length but the UI does not update. Send stack invalidated event to the client to update the stack.
-rw-r--r--lldb/test/API/tools/lldb-dap/invalidated-event/Makefile3
-rw-r--r--lldb/test/API/tools/lldb-dap/invalidated-event/TestDAP_invalidatedEvent.py55
-rw-r--r--lldb/test/API/tools/lldb-dap/invalidated-event/main.cpp9
-rw-r--r--lldb/test/API/tools/lldb-dap/invalidated-event/other.h10
-rw-r--r--lldb/tools/lldb-dap/DAP.cpp19
-rw-r--r--lldb/tools/lldb-dap/DAP.h1
-rw-r--r--lldb/tools/lldb-dap/EventHelper.cpp7
-rw-r--r--lldb/tools/lldb-dap/EventHelper.h5
-rw-r--r--lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp2
-rw-r--r--lldb/unittests/DAP/ProtocolTypesTest.cpp19
10 files changed, 119 insertions, 11 deletions
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 000000000000..99998b20bcb0
--- /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 000000000000..8ba56b0bb27c
--- /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 000000000000..c82f9532b751
--- /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;
+}
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 000000000000..856db446d7b5
--- /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
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index f76656e98ca0..61226cceb6db 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 a90ddf59671e..bf2c3f146a39 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -460,6 +460,7 @@ private:
/// 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 2b9ed229405a..3042d3293b48 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -276,11 +276,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 48eb5af6bd0b..be783d032a5a 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 b896eca817be..df6be06637a1 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;
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
index a5ae856a185b..8170abdd25bc 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) {