Skip to content

Commit 770cd43

Browse files
authored
[lldb-dap] Add invalidated event (#157530)
This patch fixes the problem, when after a `setVariable` request pointers and references to the variable are not updated. VSCode doesn't send a `variables` request after a `setVariable` request, so we should trigger it explicitly via`invalidated` event .Also, updated `writeMemory` request in similar way.
1 parent 6ab2b87 commit 770cd43

File tree

11 files changed

+136
-33
lines changed

11 files changed

+136
-33
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ def __init__(
215215
self.terminated: bool = False
216216
self.events: List[Event] = []
217217
self.progress_events: List[Event] = []
218+
self.invalidated_event: Optional[Event] = None
218219
self.reverse_requests: List[Request] = []
219220
self.module_events: List[Dict] = []
220221
self.sequence: int = 1
@@ -440,6 +441,8 @@ def _handle_event(self, packet: Event) -> None:
440441
elif event == "capabilities" and body:
441442
# Update the capabilities with new ones from the event.
442443
self.capabilities.update(body["capabilities"])
444+
elif event == "invalidated":
445+
self.invalidated_event = packet
443446

444447
def _handle_reverse_request(self, request: Request) -> None:
445448
if request in self.reverse_requests:
@@ -1014,6 +1017,7 @@ def request_initialize(self, sourceInitFile=False):
10141017
"supportsVariableType": True,
10151018
"supportsStartDebuggingRequest": True,
10161019
"supportsProgressReporting": True,
1020+
"supportsInvalidatedEvent": True,
10171021
"$__lldb_sourceInitFile": sourceInitFile,
10181022
},
10191023
}

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,13 @@ def verify_commands(self, flavor: str, output: str, commands: list[str]):
241241
f"Command '{flavor}' - '{cmd}' not found in output: {output}",
242242
)
243243

244+
def verify_invalidated_event(self, expected_areas):
245+
event = self.dap_server.invalidated_event
246+
self.dap_server.invalidated_event = None
247+
self.assertIsNotNone(event)
248+
areas = event["body"].get("areas", [])
249+
self.assertEqual(set(expected_areas), set(areas))
250+
244251
def get_dict_value(self, d: dict, key_path: list[str]) -> Any:
245252
"""Verify each key in the key_path array is in contained in each
246253
dictionary within "d". Assert if any key isn't in the
@@ -352,13 +359,20 @@ def get_local_as_int(self, name, threadId=None):
352359
else:
353360
return int(value)
354361

362+
def set_variable(self, varRef, name, value, id=None):
363+
"""Set a variable."""
364+
response = self.dap_server.request_setVariable(varRef, name, str(value), id=id)
365+
if response["success"]:
366+
self.verify_invalidated_event(["variables"])
367+
return response
368+
355369
def set_local(self, name, value, id=None):
356370
"""Set a top level local variable only."""
357-
return self.dap_server.request_setVariable(1, name, str(value), id=id)
371+
return self.set_variable(1, name, str(value), id=id)
358372

359373
def set_global(self, name, value, id=None):
360374
"""Set a top level global variable only."""
361-
return self.dap_server.request_setVariable(2, name, str(value), id=id)
375+
return self.set_variable(2, name, str(value), id=id)
362376

363377
def stepIn(
364378
self,
@@ -577,4 +591,6 @@ def writeMemory(self, memoryReference, data=None, offset=0, allowPartial=False):
577591
response = self.dap_server.request_writeMemory(
578592
memoryReference, encodedData, offset=offset, allowPartial=allowPartial
579593
)
594+
if response["success"]:
595+
self.verify_invalidated_event(["all"])
580596
return response

lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@ def test_memory_refs_set_variable(self):
7272
ptr_value = self.get_local_as_int("rawptr")
7373
self.assertIn(
7474
"memoryReference",
75-
self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
76-
"body"
77-
].keys(),
75+
self.set_local("rawptr", ptr_value + 2)["body"].keys(),
7876
)
7977

8078
@skipIfWindows

lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def do_test_scopes_variables_setVariable_evaluate(
298298
# Set a variable value whose name is synthetic, like a variable index
299299
# and verify the value by reading it
300300
variable_value = 100
301-
response = self.dap_server.request_setVariable(varRef, "[0]", variable_value)
301+
response = self.set_variable(varRef, "[0]", variable_value)
302302
# Verify dap sent the correct response
303303
verify_response = {
304304
"type": "int",
@@ -315,7 +315,7 @@ def do_test_scopes_variables_setVariable_evaluate(
315315
# Set a variable value whose name is a real child value, like "pt.x"
316316
# and verify the value by reading it
317317
varRef = varref_dict["pt"]
318-
self.dap_server.request_setVariable(varRef, "x", 111)
318+
self.set_variable(varRef, "x", 111)
319319
response = self.dap_server.request_variables(varRef, start=0, count=1)
320320
value = response["body"]["variables"][0]["value"]
321321
self.assertEqual(
@@ -341,27 +341,15 @@ def do_test_scopes_variables_setVariable_evaluate(
341341
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
342342

343343
# Now we verify that we correctly change the name of a variable with and without differentiator suffix
344-
self.assertFalse(self.dap_server.request_setVariable(1, "x2", 9)["success"])
345-
self.assertFalse(
346-
self.dap_server.request_setVariable(1, "x @ main.cpp:0", 9)["success"]
347-
)
344+
self.assertFalse(self.set_local("x2", 9)["success"])
345+
self.assertFalse(self.set_local("x @ main.cpp:0", 9)["success"])
348346

349-
self.assertTrue(
350-
self.dap_server.request_setVariable(1, "x @ main.cpp:19", 19)["success"]
351-
)
352-
self.assertTrue(
353-
self.dap_server.request_setVariable(1, "x @ main.cpp:21", 21)["success"]
354-
)
355-
self.assertTrue(
356-
self.dap_server.request_setVariable(1, "x @ main.cpp:23", 23)["success"]
357-
)
347+
self.assertTrue(self.set_local("x @ main.cpp:19", 19)["success"])
348+
self.assertTrue(self.set_local("x @ main.cpp:21", 21)["success"])
349+
self.assertTrue(self.set_local("x @ main.cpp:23", 23)["success"])
358350

359351
# The following should have no effect
360-
self.assertFalse(
361-
self.dap_server.request_setVariable(1, "x @ main.cpp:23", "invalid")[
362-
"success"
363-
]
364-
)
352+
self.assertFalse(self.set_local("x @ main.cpp:23", "invalid")["success"])
365353

366354
verify_locals["x @ main.cpp:19"]["equals"]["value"] = "19"
367355
verify_locals["x @ main.cpp:21"]["equals"]["value"] = "21"
@@ -370,7 +358,7 @@ def do_test_scopes_variables_setVariable_evaluate(
370358
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
371359

372360
# The plain x variable shold refer to the innermost x
373-
self.assertTrue(self.dap_server.request_setVariable(1, "x", 22)["success"])
361+
self.assertTrue(self.set_local("x", 22)["success"])
374362
verify_locals["x @ main.cpp:23"]["equals"]["value"] = "22"
375363

376364
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
@@ -708,9 +696,7 @@ def test_return_variables(self):
708696
self.verify_variables(verify_locals, local_variables, varref_dict)
709697
break
710698

711-
self.assertFalse(
712-
self.dap_server.request_setVariable(1, "(Return Value)", 20)["success"]
713-
)
699+
self.assertFalse(self.set_local("(Return Value)", 20)["success"])
714700

715701
@skipIfWindows
716702
def test_indexedVariables(self):

lldb/tools/lldb-dap/EventHelper.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
#include "JSONUtils.h"
1313
#include "LLDBUtils.h"
1414
#include "Protocol/ProtocolEvents.h"
15+
#include "Protocol/ProtocolRequests.h"
1516
#include "Protocol/ProtocolTypes.h"
1617
#include "lldb/API/SBFileSpec.h"
1718
#include "llvm/Support/Error.h"
19+
#include <utility>
1820

1921
#if defined(_WIN32)
2022
#define NOMINMAX
@@ -273,4 +275,13 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
273275
dap.SendJSON(llvm::json::Value(std::move(event)));
274276
}
275277

278+
void SendInvalidatedEvent(
279+
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas) {
280+
if (!dap.clientFeatures.contains(protocol::eClientFeatureInvalidatedEvent))
281+
return;
282+
protocol::InvalidatedEventBody body;
283+
body.areas = areas;
284+
dap.Send(protocol::Event{"invalidated", std::move(body)});
285+
}
286+
276287
} // namespace lldb_dap

lldb/tools/lldb-dap/EventHelper.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#define LLDB_TOOLS_LLDB_DAP_EVENTHELPER_H
1111

1212
#include "DAPForward.h"
13+
#include "Protocol/ProtocolEvents.h"
14+
#include "llvm/ADT/ArrayRef.h"
1315
#include "llvm/Support/Error.h"
1416

1517
namespace lldb_dap {
@@ -32,6 +34,9 @@ void SendContinuedEvent(DAP &dap);
3234

3335
void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process);
3436

37+
void SendInvalidatedEvent(
38+
DAP &dap, llvm::ArrayRef<protocol::InvalidatedEventBody::Area> areas);
39+
3540
} // namespace lldb_dap
3641

3742
#endif

lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "DAP.h"
1010
#include "EventHelper.h"
1111
#include "JSONUtils.h"
12+
#include "Protocol/ProtocolEvents.h"
1213
#include "RequestHandler.h"
1314

1415
using namespace lldb_dap::protocol;
@@ -77,6 +78,10 @@ SetVariableRequestHandler::Run(const SetVariableArguments &args) const {
7778
if (ValuePointsToCode(variable))
7879
body.valueLocationReference = new_var_ref;
7980

81+
// Also send invalidated event to signal client that some variables
82+
// (e.g. references) can be changed.
83+
SendInvalidatedEvent(dap, {InvalidatedEventBody::eAreaVariables});
84+
8085
return body;
8186
}
8287

lldb/tools/lldb-dap/Handler/WriteMemoryRequestHandler.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "DAP.h"
10+
#include "EventHelper.h"
1011
#include "JSONUtils.h"
12+
#include "Protocol/ProtocolEvents.h"
1113
#include "RequestHandler.h"
1214
#include "lldb/API/SBMemoryRegionInfo.h"
1315
#include "llvm/ADT/StringExtras.h"
1416
#include "llvm/Support/Base64.h"
1517

18+
using namespace lldb_dap::protocol;
19+
1620
namespace lldb_dap {
1721

1822
// Writes bytes to memory at the provided location.
1923
//
2024
// Clients should only call this request if the corresponding capability
2125
// supportsWriteMemoryRequest is true.
22-
llvm::Expected<protocol::WriteMemoryResponseBody>
23-
WriteMemoryRequestHandler::Run(
24-
const protocol::WriteMemoryArguments &args) const {
26+
llvm::Expected<WriteMemoryResponseBody>
27+
WriteMemoryRequestHandler::Run(const WriteMemoryArguments &args) const {
2528
const lldb::addr_t address = args.memoryReference + args.offset;
2629

2730
lldb::SBProcess process = dap.target.GetProcess();
@@ -91,8 +94,13 @@ WriteMemoryRequestHandler::Run(
9194
if (bytes_written == 0) {
9295
return llvm::make_error<DAPError>(write_error.GetCString());
9396
}
94-
protocol::WriteMemoryResponseBody response;
97+
WriteMemoryResponseBody response;
9598
response.bytesWritten = bytes_written;
99+
100+
// Also send invalidated event to signal client that some things
101+
// (e.g. variables) can be changed.
102+
SendInvalidatedEvent(dap, {InvalidatedEventBody::eAreaAll});
103+
96104
return response;
97105
}
98106

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,27 @@ json::Value toJSON(const ModuleEventBody &MEB) {
3333
return json::Object{{"reason", MEB.reason}, {"module", MEB.module}};
3434
}
3535

36+
llvm::json::Value toJSON(const InvalidatedEventBody::Area &IEBA) {
37+
switch (IEBA) {
38+
case InvalidatedEventBody::eAreaAll:
39+
return "all";
40+
case InvalidatedEventBody::eAreaStacks:
41+
return "stacks";
42+
case InvalidatedEventBody::eAreaThreads:
43+
return "threads";
44+
case InvalidatedEventBody::eAreaVariables:
45+
return "variables";
46+
}
47+
llvm_unreachable("unhandled invalidated event area!.");
48+
}
49+
50+
llvm::json::Value toJSON(const InvalidatedEventBody &IEB) {
51+
json::Object Result{{"areas", IEB.areas}};
52+
if (IEB.threadId)
53+
Result.insert({"threadID", IEB.threadId});
54+
if (IEB.frameId)
55+
Result.insert({"frameId", IEB.frameId});
56+
return Result;
57+
}
58+
3659
} // namespace lldb_dap::protocol

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
#define LLDB_TOOLS_LLDB_DAP_PROTOCOL_PROTOCOL_EVENTS_H
2222

2323
#include "Protocol/ProtocolTypes.h"
24+
#include "lldb/lldb-types.h"
2425
#include "llvm/Support/JSON.h"
26+
#include <cstdint>
27+
#include <optional>
28+
#include <vector>
2529

2630
namespace lldb_dap::protocol {
2731

@@ -56,6 +60,34 @@ struct ModuleEventBody {
5660
llvm::json::Value toJSON(const ModuleEventBody::Reason &);
5761
llvm::json::Value toJSON(const ModuleEventBody &);
5862

63+
/// This event signals that some state in the debug adapter has changed and
64+
/// requires that the client needs to re-render the data snapshot previously
65+
/// requested.
66+
///
67+
/// Debug adapters do not have to emit this event for runtime changes like
68+
/// stopped or thread events because in that case the client refetches the new
69+
/// state anyway. But the event can be used for example to refresh the UI after
70+
/// rendering formatting has changed in the debug adapter.
71+
///
72+
/// This event should only be sent if the corresponding capability
73+
/// supportsInvalidatedEvent is true.
74+
struct InvalidatedEventBody {
75+
enum Area : unsigned { eAreaAll, eAreaStacks, eAreaThreads, eAreaVariables };
76+
77+
/// Set of logical areas that got invalidated.
78+
std::vector<Area> areas;
79+
80+
/// If specified, the client only needs to refetch data related to this
81+
/// thread.
82+
std::optional<lldb::tid_t> threadId;
83+
84+
/// If specified, the client only needs to refetch data related to this stack
85+
/// frame (and the `threadId` is ignored).
86+
std::optional<uint64_t> frameId;
87+
};
88+
llvm::json::Value toJSON(const InvalidatedEventBody::Area &);
89+
llvm::json::Value toJSON(const InvalidatedEventBody &);
90+
5991
} // end namespace lldb_dap::protocol
6092

6193
#endif

0 commit comments

Comments
 (0)