[Lldb-commits] [lldb] [lldb-dap] Add invalidated event (PR #157530)
Druzhkov Sergei via lldb-commits
lldb-commits at lists.llvm.org
Mon Sep 8 11:37:17 PDT 2025
https://github.com/DrSergei created https://github.com/llvm/llvm-project/pull/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.
>From 35117bc744bb67834fbea112ae7e316128db779b Mon Sep 17 00:00:00 2001
From: Druzhkov Sergei <serzhdruzhok at gmail.com>
Date: Mon, 8 Sep 2025 21:14:21 +0300
Subject: [PATCH] [lldb-dap] Add invalidated event
---
.../test/tools/lldb-dap/dap_server.py | 3 ++
.../test/tools/lldb-dap/lldbdap_testcase.py | 20 +++++++++--
.../tools/lldb-dap/memory/TestDAP_memory.py | 4 +--
.../lldb-dap/variables/TestDAP_variables.py | 34 ++++++-------------
lldb/tools/lldb-dap/EventHelper.cpp | 22 ++++++++++++
lldb/tools/lldb-dap/EventHelper.h | 10 ++++++
.../Handler/SetVariableRequestHandler.cpp | 4 +++
.../Handler/WriteMemoryRequestHandler.cpp | 6 ++++
8 files changed, 74 insertions(+), 29 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 66aa070a537e0..db3efd3bcb30c 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -215,6 +215,7 @@ def __init__(
self.terminated: bool = False
self.events: List[Event] = []
self.progress_events: List[Event] = []
+ self.invalidated_event: Event = None
self.reverse_requests: List[Request] = []
self.module_events: List[Dict] = []
self.sequence: int = 1
@@ -440,6 +441,8 @@ def _handle_event(self, packet: Event) -> None:
elif event == "capabilities" and body:
# Update the capabilities with new ones from the event.
self.capabilities.update(body["capabilities"])
+ elif event == "invalidated":
+ self.invalidated_event = packet
def _handle_reverse_request(self, request: Request) -> None:
if request in self.reverse_requests:
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index fffd4c23d6fcd..a0a009ae6cc9a 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -241,6 +241,13 @@ def verify_commands(self, flavor: str, output: str, commands: list[str]):
f"Command '{flavor}' - '{cmd}' not found in output: {output}",
)
+ def verify_invalidated_event(self, expected_areas):
+ event = self.dap_server.invalidated_event
+ self.dap_server.invalidated_event = None
+ self.assertIsNotNone(event)
+ areas = event["body"].get("areas", [])
+ self.assertEqual(set(expected_areas), set(areas))
+
def get_dict_value(self, d: dict, key_path: list[str]) -> Any:
"""Verify each key in the key_path array is in contained in each
dictionary within "d". Assert if any key isn't in the
@@ -352,13 +359,20 @@ def get_local_as_int(self, name, threadId=None):
else:
return int(value)
+ def set_variable(self, varRef, name, value, id=None):
+ """Set a variable."""
+ response = self.dap_server.request_setVariable(varRef, name, str(value), id=id)
+ if response["success"]:
+ self.verify_invalidated_event(["variables"])
+ return response
+
def set_local(self, name, value, id=None):
"""Set a top level local variable only."""
- return self.dap_server.request_setVariable(1, name, str(value), id=id)
+ return self.set_variable(1, name, str(value), id=id)
def set_global(self, name, value, id=None):
"""Set a top level global variable only."""
- return self.dap_server.request_setVariable(2, name, str(value), id=id)
+ return self.set_variable(2, name, str(value), id=id)
def stepIn(
self,
@@ -577,4 +591,6 @@ def writeMemory(self, memoryReference, data=None, offset=0, allowPartial=False):
response = self.dap_server.request_writeMemory(
memoryReference, encodedData, offset=offset, allowPartial=allowPartial
)
+ if response["success"]:
+ self.verify_invalidated_event(["all"])
return response
diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
index f51056d7020c6..7c9ad0c0f75ee 100644
--- a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
+++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
@@ -72,9 +72,7 @@ def test_memory_refs_set_variable(self):
ptr_value = self.get_local_as_int("rawptr")
self.assertIn(
"memoryReference",
- self.dap_server.request_setVariable(1, "rawptr", ptr_value + 2)[
- "body"
- ].keys(),
+ self.set_local("rawptr", ptr_value + 2)["body"].keys(),
)
@skipIfWindows
diff --git a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
index a3a4bdaaf40a6..13a694602f230 100644
--- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
+++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py
@@ -298,7 +298,7 @@ def do_test_scopes_variables_setVariable_evaluate(
# Set a variable value whose name is synthetic, like a variable index
# and verify the value by reading it
variable_value = 100
- response = self.dap_server.request_setVariable(varRef, "[0]", variable_value)
+ response = self.set_variable(varRef, "[0]", variable_value)
# Verify dap sent the correct response
verify_response = {
"type": "int",
@@ -315,7 +315,7 @@ def do_test_scopes_variables_setVariable_evaluate(
# Set a variable value whose name is a real child value, like "pt.x"
# and verify the value by reading it
varRef = varref_dict["pt"]
- self.dap_server.request_setVariable(varRef, "x", 111)
+ self.set_variable(varRef, "x", 111)
response = self.dap_server.request_variables(varRef, start=0, count=1)
value = response["body"]["variables"][0]["value"]
self.assertEqual(
@@ -341,27 +341,15 @@ def do_test_scopes_variables_setVariable_evaluate(
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
# Now we verify that we correctly change the name of a variable with and without differentiator suffix
- self.assertFalse(self.dap_server.request_setVariable(1, "x2", 9)["success"])
- self.assertFalse(
- self.dap_server.request_setVariable(1, "x @ main.cpp:0", 9)["success"]
- )
+ self.assertFalse(self.set_local("x2", 9)["success"])
+ self.assertFalse(self.set_local("x @ main.cpp:0", 9)["success"])
- self.assertTrue(
- self.dap_server.request_setVariable(1, "x @ main.cpp:19", 19)["success"]
- )
- self.assertTrue(
- self.dap_server.request_setVariable(1, "x @ main.cpp:21", 21)["success"]
- )
- self.assertTrue(
- self.dap_server.request_setVariable(1, "x @ main.cpp:23", 23)["success"]
- )
+ self.assertTrue(self.set_local("x @ main.cpp:19", 19)["success"])
+ self.assertTrue(self.set_local("x @ main.cpp:21", 21)["success"])
+ self.assertTrue(self.set_local("x @ main.cpp:23", 23)["success"])
# The following should have no effect
- self.assertFalse(
- self.dap_server.request_setVariable(1, "x @ main.cpp:23", "invalid")[
- "success"
- ]
- )
+ self.assertFalse(self.set_local("x @ main.cpp:23", "invalid")["success"])
verify_locals["x @ main.cpp:19"]["equals"]["value"] = "19"
verify_locals["x @ main.cpp:21"]["equals"]["value"] = "21"
@@ -370,7 +358,7 @@ def do_test_scopes_variables_setVariable_evaluate(
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
# The plain x variable shold refer to the innermost x
- self.assertTrue(self.dap_server.request_setVariable(1, "x", 22)["success"])
+ self.assertTrue(self.set_local("x", 22)["success"])
verify_locals["x @ main.cpp:23"]["equals"]["value"] = "22"
self.verify_variables(verify_locals, self.dap_server.get_local_variables())
@@ -708,9 +696,7 @@ def test_return_variables(self):
self.verify_variables(verify_locals, local_variables, varref_dict)
break
- self.assertFalse(
- self.dap_server.request_setVariable(1, "(Return Value)", 20)["success"]
- )
+ self.assertFalse(self.set_local("(Return Value)", 20)["success"])
@skipIfWindows
def test_indexedVariables(self):
diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp
index ecd630cb530d6..68617dbc8a364 100644
--- a/lldb/tools/lldb-dap/EventHelper.cpp
+++ b/lldb/tools/lldb-dap/EventHelper.cpp
@@ -273,4 +273,26 @@ void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) {
dap.SendJSON(llvm::json::Value(std::move(event)));
}
+json::Value toJSON(const InvalidatedArea &area) {
+ switch (area) {
+ case InvalidatedArea::eInvalidatedAreaAll:
+ return "all";
+ case InvalidatedArea::eInvalidatedAreaStacks:
+ return "stacks";
+ case InvalidatedArea::eInvalidatedAreaThreads:
+ return "threads";
+ case InvalidatedArea::eInvalidatedAreaVariables:
+ return "variables";
+ }
+ llvm_unreachable("unhandled invalidated event area!.");
+}
+
+void SendInvalidatedEvent(DAP &dap, const std::vector<InvalidatedArea> &areas) {
+ llvm::json::Object event(CreateEventObject("invalidated"));
+ llvm::json::Object body;
+ body.try_emplace("areas", areas);
+ event.try_emplace("body", std::move(body));
+ dap.SendJSON(llvm::json::Value(std::move(event)));
+}
+
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h
index 592c1b81c46af..6ac0e1bb769bc 100644
--- a/lldb/tools/lldb-dap/EventHelper.h
+++ b/lldb/tools/lldb-dap/EventHelper.h
@@ -11,6 +11,7 @@
#include "DAPForward.h"
#include "llvm/Support/Error.h"
+#include <vector>
namespace lldb_dap {
struct DAP;
@@ -32,6 +33,15 @@ void SendContinuedEvent(DAP &dap);
void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process);
+enum InvalidatedArea : unsigned {
+ eInvalidatedAreaAll,
+ eInvalidatedAreaStacks,
+ eInvalidatedAreaThreads,
+ eInvalidatedAreaVariables
+};
+
+void SendInvalidatedEvent(DAP &dap, const std::vector<InvalidatedArea> &areas);
+
} // namespace lldb_dap
#endif
diff --git a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
index d07c0d6c9afa8..ab3129d1abd7b 100644
--- a/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/SetVariableRequestHandler.cpp
@@ -77,6 +77,10 @@ SetVariableRequestHandler::Run(const SetVariableArguments &args) const {
if (ValuePointsToCode(variable))
body.valueLocationReference = new_var_ref;
+ // Also send invalidated event to signal client
+ // that some variables (e.g. references) can be changed
+ SendInvalidatedEvent(dap, {InvalidatedArea::eInvalidatedAreaVariables});
+
return body;
}
diff --git a/lldb/tools/lldb-dap/Handler/WriteMemoryRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/WriteMemoryRequestHandler.cpp
index 313f59dceab24..fa62afb3957e5 100644
--- a/lldb/tools/lldb-dap/Handler/WriteMemoryRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/WriteMemoryRequestHandler.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
+#include "EventHelper.h"
#include "JSONUtils.h"
#include "RequestHandler.h"
#include "lldb/API/SBMemoryRegionInfo.h"
@@ -93,6 +94,11 @@ WriteMemoryRequestHandler::Run(
}
protocol::WriteMemoryResponseBody response;
response.bytesWritten = bytes_written;
+
+ // Also send invalidated event to signal client
+ // that some things can be changed (e.g. variables)
+ SendInvalidatedEvent(dap, {InvalidatedArea::eInvalidatedAreaAll});
+
return response;
}
More information about the lldb-commits
mailing list