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 <serzhdruz...@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; } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits