Skip to content

Commit 29d5407

Browse files
da-viperLukacma
authored andcommitted
[lldb-dap] Send an Invalidated event on thread stack change. (llvm#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.
1 parent 04cb5f5 commit 29d5407

File tree

10 files changed

+119
-11
lines changed

10 files changed

+119
-11
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Test lldb-dap recieves invalidated-events when the area such as
3+
stack, variables, threads has changes but the client does not
4+
know about it.
5+
"""
6+
7+
import lldbdap_testcase
8+
from lldbsuite.test.lldbtest import line_number
9+
from dap_server import Event
10+
11+
12+
class TestDAP_invalidatedEvent(lldbdap_testcase.DAPTestCaseBase):
13+
def verify_top_frame_name(self, frame_name: str):
14+
all_frames = self.get_stackFrames()
15+
self.assertGreaterEqual(len(all_frames), 1, "Expected at least one frame.")
16+
top_frame_name = all_frames[0]["name"]
17+
self.assertRegex(top_frame_name, f"{frame_name}.*")
18+
19+
def test_invalidated_stack_area_event(self):
20+
"""
21+
Test an invalidated event for the stack area.
22+
The event is sent when the command `thread return <expr>` is sent by the user.
23+
"""
24+
other_source = "other.h"
25+
return_bp_line = line_number(other_source, "// thread return breakpoint")
26+
27+
program = self.getBuildArtifact("a.out")
28+
self.build_and_launch(program)
29+
self.set_source_breakpoints(other_source, [return_bp_line])
30+
self.continue_to_next_stop()
31+
32+
self.verify_top_frame_name("add")
33+
thread_id = self.dap_server.get_thread_id()
34+
self.assertIsNotNone(thread_id, "Exepected a thread id.")
35+
36+
# run thread return
37+
thread_command = "thread return 20"
38+
eval_resp = self.dap_server.request_evaluate(thread_command, context="repl")
39+
self.assertTrue(eval_resp["success"], f"Failed to evaluate `{thread_command}`.")
40+
41+
# wait for the invalidated stack event.
42+
stack_event = self.dap_server.wait_for_event(["invalidated"])
43+
self.assertIsNotNone(stack_event, "Expected an invalidated event.")
44+
event_body: Event = stack_event["body"]
45+
self.assertIn("stacks", event_body["areas"])
46+
self.assertIn("threadId", event_body.keys())
47+
self.assertEqual(
48+
thread_id,
49+
event_body["threadId"],
50+
f"Expected the event from thread {thread_id}.",
51+
)
52+
53+
# confirm we are back at the main frame.
54+
self.verify_top_frame_name("main")
55+
self.continue_to_exit()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "other.h"
2+
3+
int main() {
4+
int first = 5;
5+
int second = 10;
6+
const int result = add(first, second);
7+
8+
return 0;
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef OTHER_H
2+
#define OTHER_H
3+
4+
int add(int a, int b) {
5+
int first = a;
6+
int second = b; // thread return breakpoint
7+
int result = first + second;
8+
return result;
9+
}
10+
#endif // OTHER_H

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,12 @@ void DAP::EventThread() {
13681368
broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
13691369
debugger.GetBroadcaster().AddListener(
13701370
listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning);
1371+
1372+
// listen for thread events.
1373+
listener.StartListeningForEventClass(
1374+
debugger, lldb::SBThread::GetBroadcasterClassName(),
1375+
lldb::SBThread::eBroadcastBitStackChanged);
1376+
13711377
bool done = false;
13721378
while (!done) {
13731379
if (listener.WaitForEvent(1, event)) {
@@ -1503,6 +1509,9 @@ void DAP::EventThread() {
15031509
SendJSON(llvm::json::Value(std::move(bp_event)));
15041510
}
15051511
}
1512+
1513+
} else if (lldb::SBThread::EventIsThreadEvent(event)) {
1514+
HandleThreadEvent(event);
15061515
} else if (event_mask & lldb::eBroadcastBitError ||
15071516
event_mask & lldb::eBroadcastBitWarning) {
15081517
lldb::SBStructuredData data =
@@ -1522,6 +1531,16 @@ void DAP::EventThread() {
15221531
}
15231532
}
15241533

1534+
void DAP::HandleThreadEvent(const lldb::SBEvent &event) {
1535+
uint32_t event_type = event.GetType();
1536+
1537+
if (event_type & lldb::SBThread::eBroadcastBitStackChanged) {
1538+
const lldb::SBThread evt_thread = lldb::SBThread::GetThreadFromEvent(event);
1539+
SendInvalidatedEvent(*this, {InvalidatedEventBody::eAreaStacks},
1540+
evt_thread.GetThreadID());
1541+
}
1542+
}
1543+
15251544
std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints(
15261545
const protocol::Source &source,
15271546
const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) {

lldb/tools/lldb-dap/DAP.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ struct DAP final : public DAPTransport::MessageHandler {
460460
/// Event threads.
461461
/// @{
462462
void EventThread();
463+
void HandleThreadEvent(const lldb::SBEvent &event);
463464
void ProgressEventThread();
464465

465466
std::thread event_thread;

lldb/tools/lldb-dap/EventHelper.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,16 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
276276
}
277277

278278
void SendInvalidatedEvent(
279-
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas) {
279+
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas,
280+
lldb::tid_t tid) {
280281
if (!dap.clientFeatures.contains(protocol::eClientFeatureInvalidatedEvent))
281282
return;
282283
protocol::InvalidatedEventBody body;
283284
body.areas = areas;
285+
286+
if (tid != LLDB_INVALID_THREAD_ID)
287+
body.threadId = tid;
288+
284289
dap.Send(protocol::Event{"invalidated", std::move(body)});
285290
}
286291

lldb/tools/lldb-dap/EventHelper.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include "DAPForward.h"
1313
#include "Protocol/ProtocolEvents.h"
14+
#include "lldb/lldb-defines.h"
15+
#include "lldb/lldb-types.h"
1416
#include "llvm/ADT/ArrayRef.h"
1517
#include "llvm/Support/Error.h"
1618

@@ -35,7 +37,8 @@ void SendContinuedEvent(DAP &dap);
3537
void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process);
3638

3739
void SendInvalidatedEvent(
38-
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas);
40+
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas,
41+
lldb::tid_t tid = LLDB_INVALID_THREAD_ID);
3942

4043
void SendMemoryEvent(DAP &dap, lldb::SBValue variable);
4144

lldb/tools/lldb-dap/Protocol/ProtocolEvents.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ llvm::json::Value toJSON(const InvalidatedEventBody::Area &IEBA) {
5151
llvm::json::Value toJSON(const InvalidatedEventBody &IEB) {
5252
json::Object Result{{"areas", IEB.areas}};
5353
if (IEB.threadId)
54-
Result.insert({"threadID", IEB.threadId});
54+
Result.insert({"threadId", IEB.threadId});
5555
if (IEB.stackFrameId)
5656
Result.insert({"stackFrameId", IEB.stackFrameId});
5757
return Result;

lldb/unittests/DAP/ProtocolTypesTest.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,14 +1079,17 @@ TEST(ProtocolTypesTest, InvalidatedEventBody) {
10791079
body.areas = {InvalidatedEventBody::eAreaStacks,
10801080
InvalidatedEventBody::eAreaThreads};
10811081
body.stackFrameId = 1;
1082-
StringRef json = R"({
1083-
"areas": [
1084-
"stacks",
1085-
"threads"
1086-
],
1087-
"stackFrameId": 1
1088-
})";
1089-
EXPECT_EQ(json, pp(body));
1082+
body.threadId = 20;
1083+
Expected<json::Value> expected = json::parse(R"({
1084+
"areas": [
1085+
"stacks",
1086+
"threads"
1087+
],
1088+
"stackFrameId": 1,
1089+
"threadId": 20
1090+
})");
1091+
ASSERT_THAT_EXPECTED(expected, llvm::Succeeded());
1092+
EXPECT_EQ(pp(*expected), pp(body));
10901093
}
10911094

10921095
TEST(ProtocolTypesTest, MemoryEventBody) {

0 commit comments

Comments
 (0)