https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/180858
>From 2000bbe4c9ec8cc19e776273dbe4459106f4f505 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Thu, 29 Jan 2026 16:22:19 -0800 Subject: [PATCH 1/5] [lldb-dap] IO Handling refactor to support raw input mode. Updating lldb-dap to use a pty (when supported) for the debugger input/output handles. This enables us to support raw-input mode commands like `script` or `breakpoint command add`. As a fallback on platforms that don't support a pty we use a pipe, which also works but has some usability issues. With a pipe, we do NOT see the prompt and some commands do not show us any helpful information, e.g. `breakpoint command add` does NOT tell you to use `DONE` to end the command. The command does work, just not as helpful as with a pty. With a pty we see the prompt in the console output and it often shows up in the command output as well. --- lldb/packages/Python/lldbsuite/test/dotest.py | 11 +- .../test/tools/lldb-dap/dap_server.py | 40 ++- .../test/tools/lldb-dap/lldbdap_testcase.py | 4 +- .../Interpreter/embedded_interpreter.py | 3 + .../tools/lldb-dap/cancel/TestDAP_cancel.py | 41 +-- .../API/tools/lldb-dap/cancel/busy_loop.py | 17 ++ .../completions/TestDAP_completions.py | 3 +- .../tools/lldb-dap/console/TestDAP_console.py | 4 + .../lldb-dap/evaluate/TestDAP_evaluate.py | 10 +- .../lldb-dap/launch/TestDAP_launch_version.py | 7 +- .../lldb-dap/progress/TestDAP_Progress.py | 5 +- .../repl-mode/TestDAP_repl_mode_detection.py | 13 +- .../lldb-dap/send-event/TestDAP_sendEvent.py | 3 +- .../lldb-dap/variables/TestDAP_variables.py | 8 +- lldb/tools/lldb-dap/CMakeLists.txt | 1 + lldb/tools/lldb-dap/DAP.cpp | 247 ++++++++++++++---- lldb/tools/lldb-dap/DAP.h | 47 +++- lldb/tools/lldb-dap/EvaluateContext.cpp | 127 +++++++++ lldb/tools/lldb-dap/EvaluateContext.h | 48 ++++ lldb/tools/lldb-dap/EventHelper.cpp | 52 ++-- .../Handler/CompletionsRequestHandler.cpp | 1 + .../ConfigurationDoneRequestHandler.cpp | 3 + .../Handler/EvaluateRequestHandler.cpp | 106 +++----- .../Handler/VariablesRequestHandler.cpp | 62 +++-- lldb/tools/lldb-dap/ProgressEvent.cpp | 2 +- .../lldb-dap/Protocol/ProtocolRequests.cpp | 4 +- .../lldb-dap/Protocol/ProtocolRequests.h | 4 +- lldb/tools/lldb-dap/Variables.cpp | 21 ++ lldb/tools/lldb-dap/Variables.h | 4 + lldb/unittests/DAP/Handler/DisconnectTest.cpp | 11 +- lldb/unittests/DAP/TestBase.cpp | 23 +- lldb/unittests/DAP/TestBase.h | 1 - 32 files changed, 679 insertions(+), 254 deletions(-) create mode 100644 lldb/test/API/tools/lldb-dap/cancel/busy_loop.py create mode 100644 lldb/tools/lldb-dap/EvaluateContext.cpp create mode 100644 lldb/tools/lldb-dap/EvaluateContext.h diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py index 533be0a065e3a..f357db9d58f14 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -569,11 +569,12 @@ def setupSysPath(): lldbDir = os.path.dirname(lldbtest_config.lldbExec) - lldbDAPExec = os.path.join( - lldbDir, "lldb-dap.exe" if sys.platform == "win32" else "lldb-dap" - ) - if is_exe(lldbDAPExec): - os.environ["LLDBDAP_EXEC"] = lldbDAPExec + if not os.environ.get("LLDBDAP_EXEC", None): + lldbDAPExec = os.path.join( + lldbDir, "lldb-dap.exe" if sys.platform == "win32" else "lldb-dap" + ) + if is_exe(lldbDAPExec): + os.environ["LLDBDAP_EXEC"] = lldbDAPExec configuration.yaml2macho_core = shutil.which("yaml2macho-core", path=lldbDir) 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 840c087ceec4d..f3efc7b23214b 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 @@ -299,7 +299,7 @@ def __init__( self.threads: Optional[dict] = None self.stopped_thread: Optional[dict] = None self.thread_stacks: Optional[Dict[int, List[dict]]] - self.thread_stop_reasons: Dict[str, Any] = {} + self.thread_stop_reasons: Dict[int, Any] = {} self.focused_tid: Optional[int] = None self.frame_scopes: Dict[str, Any] = {} # keyed by breakpoint id @@ -486,9 +486,10 @@ def _process_recv_packets(self) -> None: with self._recv_condition: for packet in self._recv_packets: if packet and ("seq" not in packet or packet["seq"] == 0): - raise ValueError( - f"received a malformed packet, expected 'seq != 0' for {packet!r}" - ) + pass + # raise ValueError( + # f"received a malformed packet, expected 'seq != 0' for {packet!r}" + # ) if packet: self._log.messages.append((Log.Dir.RECV, packet)) # Handle events that may modify any stateful properties of @@ -540,10 +541,13 @@ def _handle_event(self, packet: Event) -> None: # reasons since the 'threads' command doesn't return # that information. self._process_stopped() - tid = body["threadId"] - self.thread_stop_reasons[tid] = body - if "preserveFocusHint" not in body or not body["preserveFocusHint"]: - self.focused_tid = tid + if "allThreadsStopped" in body and body["allThreadsStopped"]: + self.thread_stop_reasons = {} + if "threadId" in body: + tid = body["threadId"] + self.thread_stop_reasons[tid] = body + if "preserveFocusHint" not in body or not body["preserveFocusHint"]: + self.focused_tid = tid elif event.startswith("progress"): # Progress events come in as 'progressStart', 'progressUpdate', # and 'progressEnd' events. Keep these around in case test @@ -663,11 +667,13 @@ def predicate(p: ProtocolMessage): return cast(Response, self._recv_packet(predicate=predicate)) - def wait_for_event(self, filter: List[str]) -> Event: + def wait_for_event( + self, filter: List[str], pred: Callable[[Event], bool] = lambda _: True + ) -> Event: """Wait for the first event that matches the filter.""" def predicate(p: ProtocolMessage): - return p["type"] == "event" and p["event"] in filter + return p["type"] == "event" and p["event"] in filter and pred(p) return cast(Event, self._recv_packet(predicate=predicate)) @@ -706,6 +712,18 @@ def ensure_initialized(self) -> None: ), "configuration done has already been sent, 'initialized' should have already occurred" self.wait_for_initialized() + def collect_progress(self, title: str) -> List[Event]: + start_event = self.wait_for_event( + ["progressStart"], lambda event: event["body"]["title"] == title + ) + progress_id = start_event["body"]["progressId"] + self.wait_for_event( + ["progressEnd"], lambda event: event["body"]["progressId"] == progress_id + ) + return [ + e for e in self.progress_events if e["body"]["progressId"] == progress_id + ] + def wait_for_stopped(self) -> List[Event]: """Wait for the next 'stopped' event to occur, coalescing all stopped events within a given quiet period.""" return self.collect_events(["stopped", "exited"]) @@ -1162,6 +1180,8 @@ def request_evaluate( args_dict["frameId"] = stackFrame["id"] if context: + if context == "repl" and not self.configuration_done_sent: + raise ValueError("configurtionDone must be called to activate the repl") args_dict["context"] = context if is_hex is not None: args_dict["format"] = {"hex": is_hex} 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 f3c16bd849a48..daa3823cac77e 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,11 +241,13 @@ def verify_stop_exception_info( def verify_stop_on_entry(self) -> None: """Waits for the process to be stopped and then verifies at least one thread has the stop reason 'entry'.""" + if not self.dap_server.configuration_done_sent: + self.verify_configuration_done() self.dap_server.wait_for_stopped() self.assertIn( "entry", (t["reason"] for t in self.dap_server.thread_stop_reasons.values()), - "Expected at least one thread to report stop reason 'entry' in {self.dap_server.thread_stop_reasons}", + f"Expected at least one thread to report stop reason 'entry' in {self.dap_server.thread_stop_reasons}", ) def verify_commands(self, flavor: str, output: str, commands: List[str]): diff --git a/lldb/source/Interpreter/embedded_interpreter.py b/lldb/source/Interpreter/embedded_interpreter.py index 12c47bd712816..b8d0cfeccd94e 100644 --- a/lldb/source/Interpreter/embedded_interpreter.py +++ b/lldb/source/Interpreter/embedded_interpreter.py @@ -49,6 +49,8 @@ def readfunc(prompt): def readfunc_stdio(prompt): + # Flush stdout and stderr to ensure consistency. + sys.stderr.flush() sys.stdout.write(prompt) sys.stdout.flush() line = sys.stdin.readline() @@ -77,6 +79,7 @@ def run_python_interpreter(local_dict): if e.code: print("Script exited with code %s" % e.code) + def run_one_line(local_dict, input_string): global g_run_one_line_str try: diff --git a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py index 14789a6694686..8429941ad574f 100644 --- a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py +++ b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py @@ -2,10 +2,8 @@ Test lldb-dap cancel request """ +import os import time - -from lldbsuite.test.decorators import * -from lldbsuite.test.lldbtest import * import lldbdap_testcase @@ -13,25 +11,21 @@ class TestDAP_cancel(lldbdap_testcase.DAPTestCaseBase): def send_async_req(self, command: str, arguments: dict = {}) -> int: return self.dap_server.send_packet( { + "seq": 0, "type": "request", "command": command, "arguments": arguments, } ) - def async_blocking_request(self, duration: float) -> int: + def async_blocking_request(self, count: int) -> int: """ - Sends an evaluate request that will sleep for the specified duration to + Sends an evaluate request that will sleep for the specified count to block the request handling thread. """ return self.send_async_req( command="evaluate", - arguments={ - "expression": '`script import time; print("starting sleep", file=lldb.debugger.GetOutputFileHandle()); time.sleep({})'.format( - duration - ), - "context": "repl", - }, + arguments={"expression": f"`busy-loop {count}", "context": "repl"}, ) def async_cancel(self, requestId: int) -> int: @@ -42,14 +36,20 @@ def test_pending_request(self): Tests cancelling a pending request. """ program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + busy_loop = os.path.join(os.path.dirname(__file__), "busy_loop.py") + self.build_and_launch( + program, + initCommands=[f"command script import {busy_loop}"], + stopOnEntry=True, + ) + self.verify_stop_on_entry() # Use a relatively short timeout since this is only to ensure the # following request is queued. - blocking_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 10) + blocking_seq = self.async_blocking_request(count=1) # Use a longer timeout to ensure we catch if the request was interrupted # properly. - pending_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 2) + pending_seq = self.async_blocking_request(count=10) cancel_seq = self.async_cancel(requestId=pending_seq) blocking_resp = self.dap_server.receive_response(blocking_seq) @@ -74,11 +74,18 @@ def test_inflight_request(self): Tests cancelling an inflight request. """ program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + busy_loop = os.path.join(os.path.dirname(__file__), "busy_loop.py") + self.build_and_launch( + program, + initCommands=[f"command script import {busy_loop}"], + stopOnEntry=True, + ) + self.verify_configuration_done() + self.verify_stop_on_entry() - blocking_seq = self.async_blocking_request(duration=self.DEFAULT_TIMEOUT / 2) + blocking_seq = self.async_blocking_request(count=10) # Wait for the sleep to start to cancel the inflight request. - self.collect_console(pattern="starting sleep") + time.sleep(0.5) cancel_seq = self.async_cancel(requestId=blocking_seq) blocking_resp = self.dap_server.receive_response(blocking_seq) diff --git a/lldb/test/API/tools/lldb-dap/cancel/busy_loop.py b/lldb/test/API/tools/lldb-dap/cancel/busy_loop.py new file mode 100644 index 0000000000000..21108d7e32e45 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/cancel/busy_loop.py @@ -0,0 +1,17 @@ +import time +import lldb + + [email protected](command_name="busy-loop") +def busy_loop(debugger, command, exe_ctx, result, internal_dict): + """Test helper as a busy loop.""" + if not command: + command = "10" + count = int(command) + print("Starting loop...", count) + for i in range(count): + if debugger.InterruptRequested(): + print("interrupt requested, stopping loop", i) + break + print("No interrupted requested, sleeping", i) + time.sleep(1) diff --git a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py index dd5fdcc4bbad4..db7d04829f046 100644 --- a/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py +++ b/lldb/test/API/tools/lldb-dap/completions/TestDAP_completions.py @@ -377,14 +377,13 @@ def test_auto_completions(self): Tests completion requests in "repl-mode=auto" """ self.setup_debuggee() + self.continue_to_next_stop() res = self.dap_server.request_evaluate( "`lldb-dap repl-mode auto", context="repl" ) self.assertTrue(res["success"]) - self.continue_to_next_stop() - # Stopped at breakpoint 1 # 'var' variable is in scope, completions should not show any warning. self.dap_server.get_completions("var ") diff --git a/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py b/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py index ceddaeb50cd3b..cd0a8e715de42 100644 --- a/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py +++ b/lldb/test/API/tools/lldb-dap/console/TestDAP_console.py @@ -164,8 +164,12 @@ def test_exit_status_message_ok(self): ) def test_diagnositcs(self): + source = "main.cpp" program = self.getBuildArtifact("a.out") self.build_and_launch(program) + breakpoint1_line = line_number(source, "// breakpoint 1") + breakpoint_ids = self.set_source_breakpoints(source, [breakpoint1_line]) + self.continue_to_breakpoints(breakpoint_ids) core = self.getBuildArtifact("minidump.core") self.yaml2obj("minidump.yaml", core) diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py index 98ed0b9df228a..e2e940c21ad0e 100644 --- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py +++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py @@ -138,11 +138,12 @@ def run_test_evaluate_expressions( self.assertEvaluate("var1", "20", want_type="int") # Empty expression should equate to the previous expression. if context == "repl": - self.assertEvaluate("", "20") + self.assertEvaluate("", r"\(lldb\) ", want_memref=False) else: self.assertEvaluateFailure("") self.assertEvaluate("var2", "21", want_type="int") if context == "repl": + self.assertEvaluate("e var2", "21", want_memref=False) self.assertEvaluate("", "21", want_type="int") self.assertEvaluate("", "21", want_type="int") self.assertEvaluate("static_int", "0x0000002a", want_type="int", is_hex=True) @@ -210,15 +211,14 @@ def run_test_evaluate_expressions( "struct3", "0x.*0", want_varref=True, want_type="my_struct *" ) - if context == "repl" or context is None: - # In repl or unknown context expressions may be interpreted as lldb - # commands since no variables have the same name as the command. + if context == "repl": + # In repl context expressions may be interpreted as lldb commands + # since no variables have the same name as the command. self.assertEvaluate("list", r".*", want_memref=False) # Changing the frame index should not make a difference self.assertEvaluate( "version", r".*lldb.+", want_memref=False, frame_index=1 ) - else: self.assertEvaluateFailure("list") # local variable of a_function diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_version.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_version.py index a598db41595a5..b57cff45a6ec6 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_version.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_version.py @@ -30,7 +30,12 @@ def test(self): version_eval_response = self.dap_server.request_evaluate( "`version", context="repl" ) - version_eval_output = version_eval_response["body"]["result"] + version_eval_output = ( + version_eval_response["body"]["result"] + .removeprefix("(lldb) ") + .removesuffix("(lldb) ") + ) + # Strip the prompt which may show up in the repl output. version_string = self.dap_server.get_capability("$__lldb_version") self.assertEqual( diff --git a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py index 3f57dfb66024d..7a1b6608c1e43 100755 --- a/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py +++ b/lldb/test/API/tools/lldb-dap/progress/TestDAP_Progress.py @@ -21,12 +21,10 @@ def verify_progress_events( expected_not_in_message=None, only_verify_first_update=False, ): - self.dap_server.wait_for_event(["progressEnd"]) - self.assertTrue(len(self.dap_server.progress_events) > 0) start_found = False update_found = False end_found = False - for event in self.dap_server.progress_events: + for event in self.dap_server.collect_progress(expected_title): event_type = event["event"] if "progressStart" in event_type: title = event["body"]["title"] @@ -55,6 +53,7 @@ def verify_progress_events( def test(self): program = self.getBuildArtifact("a.out") self.build_and_launch(program, stopOnEntry=True) + self.verify_stop_on_entry() progress_emitter = os.path.join(os.getcwd(), "Progress_emitter.py") self.dap_server.request_evaluate( f"`command script import {progress_emitter}", context="repl" diff --git a/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py b/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py index c6f59949d668e..d2d99b6457b77 100644 --- a/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py +++ b/lldb/test/API/tools/lldb-dap/repl-mode/TestDAP_repl_mode_detection.py @@ -3,8 +3,6 @@ """ import lldbdap_testcase -import dap_server -from lldbsuite.test import lldbutil from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * @@ -20,20 +18,23 @@ def assertEvaluate(self, expression, regex): def test_completions(self): program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + self.build_and_launch(program, stopOnEntry=True) source = "main.cpp" breakpoint1_line = line_number(source, "// breakpoint 1") breakpoint2_line = line_number(source, "// breakpoint 2") self.set_source_breakpoints(source, [breakpoint1_line, breakpoint2_line]) + self.verify_stop_on_entry() # The result of the commands should return the empty string. - self.assertEvaluate("`command regex user_command s/^$/platform/", r"^$") - self.assertEvaluate("`command alias alias_command platform", r"^$") + self.assertEvaluate( + "`command regex user_command s/^$/platform/", r"^\(lldb\) $" + ) + self.assertEvaluate("`command alias alias_command platform", r"^\(lldb\) $") self.assertEvaluate( "`command alias alias_command_with_arg platform select --sysroot %1 remote-linux", - r"^$", + r"^\(lldb\) $", ) self.continue_to_next_stop() diff --git a/lldb/test/API/tools/lldb-dap/send-event/TestDAP_sendEvent.py b/lldb/test/API/tools/lldb-dap/send-event/TestDAP_sendEvent.py index a01845669666f..ae211b36f7c0d 100644 --- a/lldb/test/API/tools/lldb-dap/send-event/TestDAP_sendEvent.py +++ b/lldb/test/API/tools/lldb-dap/send-event/TestDAP_sendEvent.py @@ -61,7 +61,8 @@ def test_send_internal_event(self): resp = self.dap_server.request_evaluate( "`lldb-dap send-event stopped", context="repl" ) + self.assertFalse(resp["success"], f"Expected error response in {resp}") self.assertRegex( - resp["body"]["result"], + resp["body"]["error"]["format"], r"Invalid use of lldb-dap send-event, event \"stopped\" should be handled by lldb-dap internally.", ) 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 1dbb0143e7a55..7e563d1ea402d 100644 --- a/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py +++ b/lldb/test/API/tools/lldb-dap/variables/TestDAP_variables.py @@ -139,8 +139,7 @@ def darwin_dwarf_missing_obj(self, initCommands): }, }, } - varref_dict = {} - self.verify_variables(verify_locals, locals, varref_dict) + self.verify_variables(verify_locals, locals) def do_test_scopes_variables_setVariable_evaluate( self, enableAutoVariableSummaries: bool @@ -580,8 +579,9 @@ def do_test_scopes_and_evaluate_expansion(self, enableAutoVariableSummaries: boo var_ref = expr_varref_dict[expandable_expression["name"]] response = self.dap_server.request_variables(var_ref) - self.verify_variables( - expandable_expression["children"], response["body"]["variables"] + self.assertFalse( + response["success"], + f"variable reference should be out of scope in {response}", ) # Test that frame scopes have corresponding presentation hints. diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 8566c663fcc06..47ab3669442f7 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -11,6 +11,7 @@ add_lldb_library(lldbDAP DAPError.cpp DAPLog.cpp DAPSessionManager.cpp + EvaluateContext.cpp EventHelper.cpp ExceptionBreakpoint.cpp FifoFiles.cpp diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 5ef384706aab0..4dd61d6cf6320 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -17,22 +17,21 @@ #include "LLDBUtils.h" #include "OutputRedirector.h" #include "Protocol/ProtocolBase.h" -#include "Protocol/ProtocolEvents.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "ProtocolUtils.h" -#include "Transport.h" -#include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBCommandInterpreterRunOptions.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBLanguageRuntime.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBMutex.h" #include "lldb/API/SBProcess.h" -#include "lldb/API/SBStream.h" -#include "lldb/Host/JSONTransport.h" +#include "lldb/Host/File.h" #include "lldb/Host/MainLoop.h" #include "lldb/Host/MainLoopBase.h" +#include "lldb/Host/Pipe.h" +#include "lldb/Host/PseudoTerminal.h" #include "lldb/Utility/Status.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" @@ -56,12 +55,13 @@ #include <cstdint> #include <cstdio> #include <functional> -#include <future> #include <memory> #include <mutex> #include <optional> #include <string> +#include <sys/fcntl.h> #include <thread> +#include <tuple> #include <utility> #include <variant> @@ -78,14 +78,6 @@ using namespace lldb_dap; using namespace lldb_dap::protocol; using namespace lldb_private; -namespace { -#ifdef _WIN32 -const char DEV_NULL[] = "nul"; -#else -const char DEV_NULL[] = "/dev/null"; -#endif -} // namespace - namespace lldb_dap { static std::string GetStringFromStructuredData(lldb::SBStructuredData &data, @@ -224,17 +216,92 @@ ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { return nullptr; } +llvm::Expected<std::pair<IOWrapper, IOWrapper>> +IOWrapper::CreatePseudoTerminal() { + // For testing purposes return an error to cause us to fallback to a pair of + // pipes. + if (getenv("LLDB_DAP_FORCE_REPL_PIPE")) + return llvm::createStringError("forced repl pipe"); + + lldb_private::PseudoTerminal pty; + if (auto err = pty.OpenFirstAvailablePrimary(O_RDWR | O_NOCTTY | O_CLOEXEC)) + return err; + if (auto err = pty.OpenSecondary(O_RDWR | O_NOCTTY | O_CLOEXEC)) + return err; + + lldb_private::Terminal term(pty.GetPrimaryFileDescriptor()); + if (llvm::Error err = term.SetCanonical(false)) + return err; + if (llvm::Error err = term.SetRaw()) + return err; + + int primary_fd = pty.ReleasePrimaryFileDescriptor(); + int replica_fd = pty.ReleaseSecondaryFileDescriptor(); + + return std::make_pair( + IOWrapper( + /*read=*/std::make_shared<lldb_private::NativeFile>( + primary_fd, lldb_private::File::eOpenOptionReadOnly, false), + /*write=*/std::make_shared<lldb_private::NativeFile>( + primary_fd, lldb_private::File::eOpenOptionWriteOnly, true)), + IOWrapper( + /*read=*/std::make_shared<lldb_private::NativeFile>( + replica_fd, lldb_private::File::eOpenOptionReadOnly, false), + /*write=*/std::make_shared<lldb_private::NativeFile>( + replica_fd, lldb_private::File::eOpenOptionWriteOnly, true))); +} + +// Hookup a pair of pipes such that reading from one writes to the other. +llvm::Expected<std::pair<IOWrapper, IOWrapper>> IOWrapper::CreatePipePair() { + lldb_private::Pipe primary_pipe, replica_pipe; + if (auto err = primary_pipe.CreateNew().takeError()) + return err; + if (auto err = replica_pipe.CreateNew().takeError()) + return err; + + return std::make_pair( + IOWrapper(/*read=*/ + std::make_shared<lldb_private::NativeFile>( + primary_pipe.ReleaseReadFileDescriptor(), + lldb_private::File::eOpenOptionReadOnly, false), + /*write=*/std::make_shared<lldb_private::NativeFile>( + replica_pipe.ReleaseWriteFileDescriptor(), + lldb_private::File::eOpenOptionWriteOnly, true)), + IOWrapper(/*read=*/ + std::make_shared<lldb_private::NativeFile>( + replica_pipe.ReleaseReadFileDescriptor(), + lldb_private::File::eOpenOptionReadOnly, false), + /*write=*/std::make_shared<lldb_private::NativeFile>( + primary_pipe.ReleaseWriteFileDescriptor(), + lldb_private::File::eOpenOptionWriteOnly, true))); +} + llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { - in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true); + DAP_LOG(log, "Initializing IO"); + + llvm::Expected<std::pair<IOWrapper, IOWrapper>> io = + IOWrapper::CreatePseudoTerminal(); + if (!io) { + DAP_LOG_ERROR( + log, io.takeError(), + "Falling back to a pipe for debugger IO, creating a pty failed: {0}"); + io = IOWrapper::CreatePipePair(); + if (!io) + return io.takeError(); + } + + std::tie(primary, replica) = *io; - if (auto Error = out.RedirectTo(overrideOut, [this](llvm::StringRef output) { - SendOutput(OutputType::Console, output); - })) + if (auto Error = stdout_redirect.RedirectTo( + overrideOut, [this](llvm::StringRef output) { + SendOutput(OutputType::Console, output); + })) return Error; - if (auto Error = err.RedirectTo(overrideErr, [this](llvm::StringRef output) { - SendOutput(OutputType::Console, output); - })) + if (auto Error = stderr_redirect.RedirectTo( + overrideErr, [this](llvm::StringRef output) { + SendOutput(OutputType::Console, output); + })) return Error; return llvm::Error::success(); @@ -596,6 +663,9 @@ lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) { ReplMode DAP::DetectReplMode(lldb::SBFrame &frame, std::string &expression, bool partial_expression) { + if (!debugger.GetCommandInterpreter().IsActive() || expression.empty()) + return ReplMode::Command; + // Check for the escape hatch prefix. if (llvm::StringRef expr_ref = expression; expr_ref.consume_front(configuration.commandEscapePrefix)) { @@ -606,6 +676,9 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame &frame, std::string &expression, if (repl_mode != ReplMode::Auto) return repl_mode; + if (expression.empty() && (repl_mode != ReplMode::Variable)) + return ReplMode::Command; + // We cannot check if expression is a variable without a frame. if (!frame) return ReplMode::Command; @@ -947,6 +1020,7 @@ llvm::Error DAP::Disconnect(bool terminateDebuggee) { } SendTerminatedEvent(); + debugger.Clear(); TerminateLoop(); return ToError(error); } @@ -959,7 +1033,7 @@ bool DAP::IsCancelled(const protocol::Request &req) { void DAP::ClearCancelRequest(const CancelArguments &args) { std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex); if (args.requestId) - m_cancelled_requests.erase(*args.requestId); + m_cancelled_requests.erase(args.requestId); } template <typename T> @@ -990,7 +1064,7 @@ void DAP::Received(const protocol::Request &request) { { std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex); if (cancel_args->requestId) - m_cancelled_requests.insert(*cancel_args->requestId); + m_cancelled_requests.insert(cancel_args->requestId); } // If a cancel is requested for the active request, make a best @@ -1000,6 +1074,22 @@ void DAP::Received(const protocol::Request &request) { DAP_LOG(log, "interrupting inflight request (command={0} seq={1})", m_active_request->command, m_active_request->seq); debugger.RequestInterrupt(); + debugger.DispatchInputInterrupt(); + debugger.GetCommandInterpreter().InterruptCommand(); + } + + // Making a best effort attempt at interrupting in-progress events. + if (!cancel_args->progressId.empty()) { + DAP_LOG(log, "cancel in progress event, interrupt debugger"); + // FIXME: Which of these are actually required, they all have different + // behavior. + debugger.RequestInterrupt(); + debugger.DispatchInputInterrupt(); + debugger.GetCommandInterpreter().InterruptCommand(); + + std::lock_guard<std::mutex> guard(m_eval_mutex); + if (m_evaluate_context) + m_evaluate_context->Interrupt(); } } @@ -1055,6 +1145,40 @@ void DAP::TransportHandler() { return; } + Status status; + m_out_handle = m_loop.RegisterReadObject( + primary.read, + [this](auto &loop) { + std::array<char, OutputBufferSize> buf = {0}; + size_t num_bytes = buf.size(); + if (auto err = primary.read->Read(buf.data(), num_bytes).takeError()) { + DAP_LOG_ERROR(log, std::move(err), "read failed {0}"); + return; + } + + // EOF detected + if (num_bytes == 0) { + m_out_handle.reset(); + return; + } + + std::lock_guard<std::mutex> guard(m_eval_mutex); + + if (m_evaluate_context && + m_evaluate_context->HandleOutput({buf.data(), num_bytes})) + return; + + SendOutput(OutputType::Console, {buf.data(), num_bytes}); + }, + status); + + if (llvm::Error err = status.takeError()) { + DAP_LOG_ERROR(log, std::move(err), "registering pty failed: {0}"); + std::lock_guard<std::mutex> guard(m_queue_mutex); + m_error_occurred = true; + return; + } + if (Status status = m_loop.Run(); status.Fail()) { DAP_LOG_ERROR(log, status.takeError(), "MainLoop run failed: {0}"); std::lock_guard<std::mutex> guard(m_queue_mutex); @@ -1063,6 +1187,17 @@ void DAP::TransportHandler() { } } +void DAP::ActivateRepl() { + lldb::SBCommandInterpreterRunOptions options; + options.SetSpawnThread(true); + options.SetAutoHandleEvents(false); + options.SetEchoCommands(false); + options.SetEchoCommentCommands(false); + options.SetAllowRepeats(true); + options.SetAddToHistory(true); + debugger.RunCommandInterpreter(options); +} + llvm::Error DAP::Loop() { { // Reset disconnect flag once we start the loop. @@ -1074,8 +1209,8 @@ llvm::Error DAP::Loop() { llvm::scope_exit cleanup([this]() { // FIXME: Merge these into the MainLoop handler. - out.Stop(); - err.Stop(); + stdout_redirect.Stop(); + stderr_redirect.Stop(); StopEventHandlers(); // Destroy the debugger when the session ends. This will trigger the @@ -1246,6 +1381,7 @@ protocol::Capabilities DAP::GetCapabilities() { protocol::eAdapterFeatureLogPoints, protocol::eAdapterFeatureSteppingGranularity, protocol::eAdapterFeatureValueFormattingOptions, + protocol::eAdapterFeatureSingleThreadExecutionRequests, }; // Capabilities associated with specific requests. @@ -1343,18 +1479,44 @@ llvm::Error DAP::InitializeDebugger() { debugger = lldb::SBDebugger::Create(/*argument_name=*/false); // Configure input/output/error file descriptors. - debugger.SetInputFile(in); + debugger.SetInputFile(replica.read); + debugger.SetOutputFile(replica.write); + debugger.SetErrorFile(replica.write); + target = debugger.GetDummyTarget(); - llvm::Expected<int> out_fd = out.GetWriteFileDescriptor(); - if (!out_fd) - return out_fd.takeError(); - debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); + auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand( + "lldb-dap", "Commands for managing lldb-dap."); + + if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) { + cmd.AddCommand( + "start-debugging", new StartDebuggingCommand(*this), + "Sends a startDebugging request from the debug adapter to the client " + "to start a child debug session of the same type as the caller."); + } + + cmd.AddCommand( + "repl-mode", new ReplModeCommand(*this), + "Get or set the repl behavior of lldb-dap evaluation requests."); + cmd.AddCommand("send-event", new SendEventCommand(*this), + "Sends an DAP event to the client."); + + StartEventThreads(); + + // Set the print callback to allow us to intercept command return objects from + // the repl to provide a more detailed view of the output. + debugger.GetCommandInterpreter().SetPrintCallback( + [](lldb::SBCommandReturnObject &result, void *baton) { + DAP *dap = static_cast<DAP *>(baton); + std::lock_guard<std::mutex> guard(dap->m_eval_mutex); - llvm::Expected<int> err_fd = err.GetWriteFileDescriptor(); - if (!err_fd) - return err_fd.takeError(); - debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); + if (dap->m_evaluate_context && + dap->m_evaluate_context->HandleReturnObject(result)) + return lldb::eCommandReturnObjectPrintCallbackHandled; + + return lldb::eCommandReturnObjectPrintCallbackSkipped; + }, + this); // The sourceInitFile option is not part of the DAP specification. It is an // extension used by the test suite to prevent sourcing `.lldbinit` and @@ -1374,23 +1536,6 @@ llvm::Error DAP::InitializeDebugger() { if (llvm::Error err = RunPreInitCommands()) return err; - auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand( - "lldb-dap", "Commands for managing lldb-dap."); - - if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) { - cmd.AddCommand( - "start-debugging", new StartDebuggingCommand(*this), - "Sends a startDebugging request from the debug adapter to the client " - "to start a child debug session of the same type as the caller."); - } - - cmd.AddCommand( - "repl-mode", new ReplModeCommand(*this), - "Get or set the repl behavior of lldb-dap evaluation requests."); - cmd.AddCommand("send-event", new SendEventCommand(*this), - "Sends an DAP event to the client."); - - StartEventThreads(); return llvm::Error::success(); } diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 657105c8dd6b9..153f2826c8045 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -11,6 +11,7 @@ #include "DAPForward.h" #include "DAPSessionManager.h" +#include "EvaluateContext.h" #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "InstructionBreakpoint.h" @@ -23,16 +24,15 @@ #include "Transport.h" #include "Variables.h" #include "lldb/API/SBBroadcaster.h" -#include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBError.h" -#include "lldb/API/SBFile.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBMutex.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "lldb/Host/MainLoop.h" +#include "lldb/lldb-forward.h" #include "lldb/lldb-types.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" @@ -81,6 +81,20 @@ enum class ReplMode { Variable = 0, Command, Auto }; using DAPTransport = lldb_private::transport::JSONTransport<ProtocolDescriptor>; +/// Wraps a set of file handles for reading and writing to a pair of handlers. +/// Typically a pty on supported platforms or a pair of pipes as a fallback. +struct IOWrapper { + IOWrapper() = default; + IOWrapper(lldb::FileSP read, lldb::FileSP write) + : read(read), write(write) {}; + + static llvm::Expected<std::pair<IOWrapper, IOWrapper>> CreatePseudoTerminal(); + static llvm::Expected<std::pair<IOWrapper, IOWrapper>> CreatePipePair(); + + lldb::FileSP read; + lldb::FileSP write; +}; + struct DAP final : public DAPTransport::MessageHandler { friend class DAPSessionManager; @@ -89,9 +103,16 @@ struct DAP final : public DAPTransport::MessageHandler { Log &log; DAPTransport &transport; - lldb::SBFile in; - OutputRedirector out; - OutputRedirector err; + + /// pty handles used for communicating with the debugger input / output + /// IOHandler. + /// @{ + IOWrapper primary; + IOWrapper replica; + /// @} + + OutputRedirector stdout_redirect; + OutputRedirector stderr_redirect; /// Configuration specified by the launch or attach commands. protocol::Configuration configuration; @@ -148,11 +169,7 @@ struct DAP final : public DAPTransport::MessageHandler { lldb::SBFormat thread_format; llvm::unique_function<void()> on_configuration_done; - /// This is used to allow request_evaluate to handle empty expressions - /// (ie the user pressed 'return' and expects the previous expression to - /// repeat). If the previous expression was a command, it will be empty. - /// Else it will contain the last valid variable expression. - std::string last_valid_variable_expression; + void ActivateRepl(); /// The set of features supported by the connected client. llvm::DenseSet<ClientFeature> clientFeatures; @@ -422,6 +439,11 @@ struct DAP final : public DAPTransport::MessageHandler { void StartEventThread(); void StartProgressEventThread(); + void SetEvaluateContext(EvaluateContext *context) { + std::lock_guard<std::mutex> guard(m_eval_mutex); + m_evaluate_context = context; + } + /// DAP debugger initialization functions. /// @{ @@ -492,6 +514,11 @@ struct DAP final : public DAPTransport::MessageHandler { const llvm::StringRef m_client_name; + std::mutex m_eval_mutex; + /// An optional context that is used while evaluating an expressions. + EvaluateContext *m_evaluate_context = nullptr; + lldb_private::MainLoop::ReadHandleUP m_out_handle; + /// List of addresses mapped by sourceReference. std::vector<lldb::addr_t> m_source_references; std::mutex m_source_references_mutex; diff --git a/lldb/tools/lldb-dap/EvaluateContext.cpp b/lldb/tools/lldb-dap/EvaluateContext.cpp new file mode 100644 index 0000000000000..3570a08bc800a --- /dev/null +++ b/lldb/tools/lldb-dap/EvaluateContext.cpp @@ -0,0 +1,127 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "EvaluateContext.h" +#include "DAP.h" +#include "DAPLog.h" +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBCommandReturnObject.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBMutex.h" +#include "lldb/API/SBProgress.h" +#include "lldb/Host/File.h" +#include "lldb/Utility/Status.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include <chrono> +#include <memory> +#include <string> + +using namespace llvm; +using namespace lldb_dap; +using lldb_private::Status; + +EvaluateContext::EvaluateContext(DAP &dap, StringRef expr) + : m_dap(dap), m_expr(expr), + m_wants_return_object(dap.debugger.GetCommandInterpreter().IsActive()) {} + +void EvaluateContext::Interrupt() { + DAP_LOG(m_dap.log, "EvaluateContext::Interrupt"); + Done(/*immediate=*/true); +} + +bool EvaluateContext::WantsRawInput() { + return !m_dap.debugger.GetCommandInterpreter().IsActive(); +} + +void EvaluateContext::Done(bool immediate) { + DAP_LOG(m_dap.log, "EvaluateContext::Done"); + if (immediate) + m_loop.AddPendingCallback([](auto &loop) { loop.RequestTermination(); }); + else + m_loop.AddCallback([](auto &loop) { loop.RequestTermination(); }, + std::chrono::milliseconds(50)); +} + +bool EvaluateContext::HandleOutput(StringRef o) { + DAP_LOG(m_dap.log, "EvaluateContext::HandleOutput(o={0})", o); + // Skip the echo of the input + if (o.trim() == m_expr.trim() && !m_echo_detected) + m_echo_detected = true; + else + m_output += o; + + if (m_wants_return_object) { + if (m_return_object_reported) + Done(/*immediate=*/m_output.ends_with(m_dap.debugger.GetPrompt())); + } else { + Done(/*immediate=*/m_output.ends_with(m_dap.debugger.GetPrompt())); + } + + return true; +} + +bool EvaluateContext::HandleReturnObject(lldb::SBCommandReturnObject &result) { + DAP_LOG(m_dap.log, "EvaluateContext::HandleReturnObject"); + m_success = result.Succeeded(); + + m_return_object_reported = true; + + if (result.GetStatus() == lldb::eReturnStatusSuccessFinishResult) + m_variables = result.GetValues(lldb::eDynamicDontRunTarget); + if (result.GetOutputSize()) + m_output += StringRef{result.GetOutput(), result.GetOutputSize()}; + if (result.GetErrorSize()) + m_output += StringRef{result.GetError(), result.GetErrorSize()}; + + if (WantsRawInput()) + Done(/*immediate=*/false); + else + Done(/*immediate=*/!m_output.empty()); + + return true; +} + +Expected<std::pair<std::string, lldb::SBValueList>> +EvaluateContext::Run(DAP &dap, StringRef expr) { + DAP_LOG(dap.log, "EvaluateContext::Run"); + EvaluateContext context(dap, expr); + dap.SetEvaluateContext(&context); + + lldb::SBProgress progress(/*title=*/"Evaluating expression", + expr.str().data(), dap.debugger); + + lldb::SBMutex api_mutex = dap.GetAPIMutex(); + + // While in raw input mode, don't wait for output in case the IOHandler never + // writes. + if (context.WantsRawInput() && !dap.primary.write->GetIsInteractive()) + context.Done(false); + + // Unlock to allow the background thread to handle reading/processing. + api_mutex.unlock(); + + size_t size = expr.size(); + if (Error err = dap.primary.write->Write(expr.data(), size).takeError()) + return err; + + Status status = context.m_loop.Run(); + + api_mutex.lock(); + dap.SetEvaluateContext(nullptr); + + if (auto error = status.takeError()) + return error; + + if (!context.m_success) + return createStringError(context.m_output.empty() + ? "Error evaluating expression" + : context.m_output.str()); + + return std::make_pair(context.m_output.str().str(), context.m_variables); +} diff --git a/lldb/tools/lldb-dap/EvaluateContext.h b/lldb/tools/lldb-dap/EvaluateContext.h new file mode 100644 index 0000000000000..c4679c696ca4f --- /dev/null +++ b/lldb/tools/lldb-dap/EvaluateContext.h @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_EVALUATE_CONTEXT_H +#define LLDB_TOOLS_LLDB_DAP_EVALUATE_CONTEXT_H + +#include "DAPForward.h" +#include "lldb/API/SBValueList.h" +#include "lldb/Host/MainLoop.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +namespace lldb_dap { +class EvaluateContext { +public: + bool HandleOutput(llvm::StringRef); + bool HandleReturnObject(lldb::SBCommandReturnObject &); + static llvm::Expected<std::pair<std::string, lldb::SBValueList>> + Run(DAP &dap, llvm::StringRef expr); + + void Interrupt(); + +private: + bool WantsRawInput(); + void Done(bool immediate); + EvaluateContext(DAP &dap, llvm::StringRef expr); + + DAP &m_dap; + llvm::StringRef m_expr; + llvm::SmallString<32> m_output; + lldb::SBValueList m_variables; + bool m_echo_detected = false; + bool m_success = true; + bool m_return_object_reported = false; + // If the CommandInterpreter is not active, then we're in raw input mode and + // will not receive a result from the print object helper. + bool m_wants_return_object = false; + lldb_private::MainLoop m_loop; +}; + +} // namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index dbf4823408b11..6831c2202c50a 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -27,6 +27,7 @@ #include "lldb/API/SBPlatform.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBThread.h" +#include "lldb/API/SBThreadCollection.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-types.h" #include "llvm/Support/Error.h" @@ -181,9 +182,10 @@ void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { static void SendStoppedEvent(DAP &dap, lldb::SBThread &thread, bool on_entry, bool all_threads_stopped, bool preserve_focus) { protocol::StoppedEventBody body; + body.reason = protocol::eStoppedReasonPause; if (on_entry) { body.reason = protocol::eStoppedReasonEntry; - } else { + } else if (thread.IsValid()) { switch (thread.GetStopReason()) { case lldb::eStopReasonTrace: case lldb::eStopReasonPlanComplete: @@ -235,13 +237,14 @@ static void SendStoppedEvent(DAP &dap, lldb::SBThread &thread, bool on_entry, case lldb::eStopReasonThreadExiting: case lldb::eStopReasonInvalid: case lldb::eStopReasonNone: - return; + break; } + + lldb::SBStream description; + thread.GetStopDescription(description); + body.description = {description.GetData(), description.GetSize()}; } lldb::tid_t tid = thread.GetThreadID(); - lldb::SBStream description; - thread.GetStopDescription(description); - body.description = {description.GetData(), description.GetSize()}; body.threadId = tid; body.allThreadsStopped = all_threads_stopped; body.preserveFocusHint = preserve_focus; @@ -266,7 +269,8 @@ llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { llvm::DenseSet<lldb::tid_t> old_thread_ids; old_thread_ids.swap(dap.thread_ids); - lldb::tid_t focused_tid = LLDB_INVALID_THREAD_ID; + lldb::SBThread focused_thread; + std::vector<lldb::SBThread> stopped_threads; for (auto thread : process) { // Collect all known thread ids for sending thread events. dap.thread_ids.insert(thread.GetThreadID()); @@ -274,22 +278,28 @@ llvm::Error SendThreadStoppedEvent(DAP &dap, bool on_entry) { if (!ThreadHasStopReason(thread)) continue; - // When we stop, report allThreadsStopped for the *first* stopped thread to - // ensure the list of stopped threads is up to date. - bool first_stop = focused_tid == LLDB_INVALID_THREAD_ID; - SendStoppedEvent(dap, thread, on_entry, /*all_threads_stopped=*/first_stop, - /*preserve_focus=*/!first_stop); - - // Default focus to the first stopped thread. - if (focused_tid == LLDB_INVALID_THREAD_ID) - focused_tid = thread.GetThreadID(); + // Focus on the first stopped thread if the selected thread is not stopped. + if (!focused_thread.IsValid()) + focused_thread = thread; + else + stopped_threads.push_back(thread); } - if (focused_tid == LLDB_INVALID_THREAD_ID) + if (!focused_thread) + focused_thread = process.GetSelectedThread(); + + if (!focused_thread) return make_error<DAPError>("no stopped threads"); + // Notify the focused thread first and tell the client all threads have + // stopped. + SendStoppedEvent(dap, focused_thread, on_entry, true, false); + // Send stopped events for remaining stopped threads. + for (auto thread : stopped_threads) + SendStoppedEvent(dap, thread, on_entry, false, true); + // Update focused thread. - dap.focus_tid = focused_tid; + dap.focus_tid = focused_thread.GetThreadID(); for (const auto &tid : old_thread_ids) if (!dap.thread_ids.contains(tid)) @@ -314,15 +324,11 @@ void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) { // Send a "continued" event to indicate the process is in the running state. void SendContinuedEvent(DAP &dap) { lldb::SBProcess process = dap.target.GetProcess(); - if (!process.IsValid()) { - return; - } - // If the focus thread is not set then we haven't reported any thread status // to the client, so nothing to report. - if (!dap.configuration_done || dap.focus_tid == LLDB_INVALID_THREAD_ID) { + if (!process.IsValid() || !dap.configuration_done || + dap.focus_tid == LLDB_INVALID_THREAD_ID) return; - } llvm::json::Object event(CreateEventObject("continued")); llvm::json::Object body; diff --git a/lldb/tools/lldb-dap/Handler/CompletionsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/CompletionsRequestHandler.cpp index 423043e6c26dd..4988ccb616b48 100644 --- a/lldb/tools/lldb-dap/Handler/CompletionsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/CompletionsRequestHandler.cpp @@ -11,6 +11,7 @@ #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBStringList.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/ConvertUTF.h" diff --git a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp index 4b84a047cd148..8e589ed68c65c 100644 --- a/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ConfigurationDoneRequestHandler.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "DAPLog.h" #include "EventHelper.h" #include "LLDBUtils.h" #include "Protocol/ProtocolRequests.h" @@ -74,6 +75,8 @@ void ConfigurationDoneRequestHandler::PostRun() const { dap.on_configuration_done(); // Clear the callback to ensure any captured resources are released. dap.on_configuration_done = nullptr; + + dap.ActivateRepl(); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp index 0eb5c57732350..5f2ac55f6af0a 100644 --- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp @@ -14,9 +14,11 @@ #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "lldb/API/SBValue.h" #include "lldb/lldb-enumerations.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" +#include <cstddef> using namespace llvm; using namespace lldb_dap; @@ -24,25 +26,6 @@ using namespace lldb_dap::protocol; namespace lldb_dap { -static bool RunExpressionAsLLDBCommand(DAP &dap, lldb::SBFrame &frame, - std::string &expression, - EvaluateContext context) { - if (context != eEvaluateContextRepl && context != eEvaluateContextUnknown) - return false; - - // Since we don't know this context do not try to repeat the last command; - if (context == eEvaluateContextUnknown && expression.empty()) - return false; - - const bool repeat_last_command = - expression.empty() && dap.last_valid_variable_expression.empty(); - if (repeat_last_command) - return true; - - const ReplMode repl_mode = dap.DetectReplMode(frame, expression, false); - return repl_mode == ReplMode::Command; -} - static lldb::SBValue EvaluateVariableExpression(lldb::SBTarget &target, lldb::SBFrame &frame, const std::string &expression, @@ -73,65 +56,60 @@ static lldb::SBValue EvaluateVariableExpression(lldb::SBTarget &target, /// The expression has access to any variables and arguments that are in scope. Expected<EvaluateResponseBody> EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const { - EvaluateResponseBody body; lldb::SBFrame frame = dap.GetLLDBFrame(arguments.frameId); - std::string expression = llvm::StringRef(arguments.expression).trim().str(); - const EvaluateContext evaluate_context = arguments.context; - const bool is_repl_context = evaluate_context == eEvaluateContextRepl; - - if (RunExpressionAsLLDBCommand(dap, frame, expression, evaluate_context)) { - // Since the current expression is not for a variable, clear the - // last_valid_variable_expression field. - dap.last_valid_variable_expression.clear(); + std::string expression = arguments.expression; + const bool is_repl_context = arguments.context == eEvaluateContextRepl; + const bool run_as_expression = arguments.context != eEvaluateContextHover; + lldb::SBValue value; + + if (arguments.context == protocol::eEvaluateContextRepl && + dap.DetectReplMode(frame, expression, false) == ReplMode::Command) { + const lldb::StateType process_state = dap.target.GetProcess().GetState(); + if (!lldb::SBDebugger::StateIsStoppedState(process_state)) + return llvm::make_error<DAPError>( + "Cannot evaluate expressions while the process is running. Pause " + "the process and try again.", + /**error_code=*/llvm::inconvertibleErrorCode(), + /**show_user=*/false); + // If we're evaluating a command relative to the current frame, set the // focus_tid to the current frame for any thread related events. - if (frame.IsValid()) { + if (frame.IsValid()) dap.focus_tid = frame.GetThread().GetThreadID(); - } - bool required_command_failed = false; - body.result = RunLLDBCommands( - dap.debugger, llvm::StringRef(), {expression}, required_command_failed, - /*parse_command_directives=*/false, /*echo_commands=*/false); - return body; - } + Expected<std::pair<std::string, lldb::SBValueList>> result = + EvaluateContext::Run(dap, expression + "\n"); + if (!result) + return result.takeError(); - const lldb::StateType process_state = dap.target.GetProcess().GetState(); - if (!lldb::SBDebugger::StateIsStoppedState(process_state)) - return llvm::make_error<DAPError>( - "Cannot evaluate expressions while the process is running. Pause " - "the process and try again.", - /**error_code=*/llvm::inconvertibleErrorCode(), - /**show_user=*/false); + lldb::SBValueList values; + std::tie(body.result, values) = *result; - // If the user expression is empty, evaluate the last valid variable - // expression. - if (expression.empty() && is_repl_context) - expression = dap.last_valid_variable_expression; + if (values.GetSize() == 1) { + value = values.GetValueAtIndex(0); + body.type = value.GetDisplayTypeName(); + } else if (values.GetSize()) { + body.variablesReference = dap.variables.Insert(result->second); + } + } else { + value = EvaluateVariableExpression(dap.target, frame, expression, + run_as_expression); - const bool run_as_expression = evaluate_context != eEvaluateContextHover; - lldb::SBValue value = EvaluateVariableExpression( - dap.target, frame, expression, run_as_expression); + if (value.GetError().Fail()) + return ToError(value.GetError(), /*show_user=*/false); - if (value.GetError().Fail()) - return ToError(value.GetError(), /*show_user=*/false); + if (is_repl_context) + value = value.Persist(); - if (is_repl_context) { - // save the new variable expression - dap.last_valid_variable_expression = std::move(expression); + const bool hex = arguments.format ? arguments.format->hex : false; + VariableDescription desc( + value, dap.configuration.enableAutoVariableSummaries, hex); - // Freeze dry the value in case users expand it later in the debug console - value = value.Persist(); + body.result = desc.GetResult(arguments.context); + body.type = desc.display_type_name; } - const bool hex = arguments.format ? arguments.format->hex : false; - VariableDescription desc(value, dap.configuration.enableAutoVariableSummaries, - hex); - - body.result = desc.GetResult(evaluate_context); - body.type = desc.display_type_name; - if (value.MightHaveChildren() || ValuePointsToCode(value)) body.variablesReference = dap.variables.InsertVariable(value, /*is_permanent=*/is_repl_context); diff --git a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp index ee7cb525d9c29..665bd62f4bd97 100644 --- a/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/VariablesRequestHandler.cpp @@ -80,7 +80,7 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const { var.name = "<error>"; var.type = "const char *"; var.value = var_err; - variables.emplace_back(var); + variables.emplace_back(std::move(var)); } } const int64_t end_idx = start_idx + ((count == 0) ? num_children : count); @@ -136,35 +136,39 @@ VariablesRequestHandler::Run(const VariablesArguments &arguments) const { // We are expanding a variable that has children, so we will return its // children. lldb::SBValue variable = dap.variables.GetVariable(var_ref); - if (variable.IsValid()) { - const bool is_permanent = - dap.variables.IsPermanentVariableReference(var_ref); - auto addChild = [&](lldb::SBValue child, - std::optional<llvm::StringRef> custom_name = {}) { - if (!child.IsValid()) - return; - const int64_t child_var_ref = - dap.variables.InsertVariable(child, is_permanent); - variables.emplace_back( - CreateVariable(child, child_var_ref, hex, - dap.configuration.enableAutoVariableSummaries, - dap.configuration.enableSyntheticChildDebugging, - /*is_name_duplicated=*/false, custom_name)); - }; - const int64_t num_children = variable.GetNumChildren(); - const int64_t end_idx = start + ((count == 0) ? num_children : count); - int64_t i = start; - for (; i < end_idx && i < num_children; ++i) - addChild(variable.GetChildAtIndex(i)); - - // If we haven't filled the count quota from the request, we insert a new - // "[raw]" child that can be used to inspect the raw version of a - // synthetic member. That eliminates the need for the user to go to the - // debug console and type `frame var <variable> to get these values. - if (dap.configuration.enableSyntheticChildDebugging && - variable.IsSynthetic() && i == num_children) - addChild(variable.GetNonSyntheticValue(), "[raw]"); + if (!variable.IsValid()) { + if (!variable.IsInScope()) + return make_error<DAPError>("value is no longer in scope."); + return make_error<DAPError>("invalid variable reference"); } + + const bool is_permanent = + dap.variables.IsPermanentVariableReference(var_ref); + auto addChild = [&](lldb::SBValue child, + std::optional<llvm::StringRef> custom_name = {}) { + if (!child.IsValid()) + return; + const int64_t child_var_ref = + dap.variables.InsertVariable(child, is_permanent); + variables.emplace_back( + CreateVariable(child, child_var_ref, hex, + dap.configuration.enableAutoVariableSummaries, + dap.configuration.enableSyntheticChildDebugging, + /*is_name_duplicated=*/false, custom_name)); + }; + const int64_t num_children = variable.GetNumChildren(); + const int64_t end_idx = start + ((count == 0) ? num_children : count); + int64_t i = start; + for (; i < end_idx && i < num_children; ++i) + addChild(variable.GetChildAtIndex(i)); + + // If we haven't filled the count quota from the request, we insert a new + // "[raw]" child that can be used to inspect the raw version of a + // synthetic member. That eliminates the need for the user to go to the + // debug console and type `frame var <variable> to get these values. + if (dap.configuration.enableSyntheticChildDebugging && + variable.IsSynthetic() && i == num_children) + addChild(variable.GetNonSyntheticValue(), "[raw]"); } return VariablesResponseBody{std::move(variables)}; diff --git a/lldb/tools/lldb-dap/ProgressEvent.cpp b/lldb/tools/lldb-dap/ProgressEvent.cpp index 5ef03ccb24bb9..33964922e1ad7 100644 --- a/lldb/tools/lldb-dap/ProgressEvent.cpp +++ b/lldb/tools/lldb-dap/ProgressEvent.cpp @@ -114,7 +114,7 @@ json::Value ProgressEvent::ToJSON() const { if (m_event_type == progressStart) { EmplaceSafeString(body, "title", m_message); - body.try_emplace("cancellable", false); + body.try_emplace("cancellable", true); } if (m_event_type == progressUpdate) diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index a72802a33fc9c..7683b8c26cbe9 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -146,8 +146,8 @@ namespace lldb_dap::protocol { bool fromJSON(const json::Value &Params, CancelArguments &CA, json::Path P) { json::ObjectMapper O(Params, P); - return O && O.map("requestId", CA.requestId) && - O.map("progressId", CA.progressId); + return O && O.mapOptional("requestId", CA.requestId) && + O.mapOptional("progressId", CA.progressId); } bool fromJSON(const json::Value &Params, DisconnectArguments &DA, diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 28c9f48200e0c..5e50322873360 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -42,13 +42,13 @@ struct CancelArguments { /// is cancelled. /// /// Both a `requestId` and a `progressId` can be specified in one request. - std::optional<int64_t> requestId; + uint64_t requestId = 0; /// The ID (attribute `progressId`) of the progress to cancel. If missing no /// progress is cancelled. /// /// Both a `requestId` and a `progressId` can be specified in one request. - std::optional<int64_t> progressId; + std::string progressId; }; bool fromJSON(const llvm::json::Value &, CancelArguments &, llvm::json::Path); diff --git a/lldb/tools/lldb-dap/Variables.cpp b/lldb/tools/lldb-dap/Variables.cpp index 6d49113243799..e030100b5c3ee 100644 --- a/lldb/tools/lldb-dap/Variables.cpp +++ b/lldb/tools/lldb-dap/Variables.cpp @@ -113,6 +113,27 @@ int64_t Variables::InsertVariable(lldb::SBValue variable, bool is_permanent) { return var_ref; } +int64_t Variables::Insert(lldb::SBValueList values) { + if (!values.IsValid() || values.GetSize() == 0) + return LLDB_DAP_INVALID_VAR_REF; + + if (values.GetSize() == 1) { + lldb::SBValue value = values.GetValueAtIndex(0); + if (value.MightHaveChildren()) + return InsertVariable(value, /*is_permanent=*/true); + return 0; + } + + int64_t var_ref = GetNewVariableReference(true); + // Copy the values as persisted results. + lldb::SBValueList persisted_values; + for (uint32_t i = 0; i < values.GetSize(); i++) { + persisted_values.Append(values.GetValueAtIndex(i).Persist()); + } + m_referenced_value_lists.insert({var_ref, persisted_values}); + return var_ref; +} + lldb::SBValue Variables::FindVariable(uint64_t variablesReference, llvm::StringRef name) { lldb::SBValue variable; diff --git a/lldb/tools/lldb-dap/Variables.h b/lldb/tools/lldb-dap/Variables.h index 9814cabc7036c..2ceb86f48c3cf 100644 --- a/lldb/tools/lldb-dap/Variables.h +++ b/lldb/tools/lldb-dap/Variables.h @@ -90,6 +90,7 @@ struct Variables { /// Insert a new \p variable. /// \return variableReference assigned to this expandable variable. int64_t InsertVariable(lldb::SBValue variable, bool is_permanent); + int64_t Insert(lldb::SBValueList values); std::optional<ScopeData> GetTopLevelScope(int64_t variablesReference); @@ -123,6 +124,9 @@ struct Variables { /// These are the variables evaluated from debug console REPL. llvm::DenseMap<int64_t, lldb::SBValue> m_referencedpermanent_variables; + /// ValueLists for evaluated commands. + llvm::DenseMap<int64_t, lldb::SBValueList> m_referenced_value_lists; + /// Key = dap_frame_id (encodes both thread index ID and frame ID) /// Value = scopes for the frame (locals, globals, registers) std::map<uint64_t, FrameScopes> m_frames; diff --git a/lldb/unittests/DAP/Handler/DisconnectTest.cpp b/lldb/unittests/DAP/Handler/DisconnectTest.cpp index 212c5698feea8..05e71534ae455 100644 --- a/lldb/unittests/DAP/Handler/DisconnectTest.cpp +++ b/lldb/unittests/DAP/Handler/DisconnectTest.cpp @@ -44,17 +44,18 @@ TEST_F(DisconnectRequestHandlerTest, DisconnectTriggersTerminateCommands) { LoadCore(); - DisconnectRequestHandler handler(*dap); - dap->configuration.terminateCommands = {"?script print(1)", "script print(2)"}; EXPECT_EQ(dap->target.GetProcess().GetState(), lldb::eStateStopped); + + DisconnectRequestHandler handler(*dap); ASSERT_THAT_ERROR(handler.Run(std::nullopt), Succeeded()); - EXPECT_CALL(client, Received(Output("1\n"))); - EXPECT_CALL(client, Received(Output("2\n"))).Times(2); - EXPECT_CALL(client, Received(Output("(lldb) script print(2)\n"))); + EXPECT_CALL(client, Received(Output("Running terminateCommands:\n"))); + EXPECT_CALL(client, Received(Output("(lldb) script print(2)\n"))); + EXPECT_CALL(client, Received(Output("2\n"))); EXPECT_CALL(client, Received(IsEvent("terminated", _))); + Run(); } #endif diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp index a9231085637c9..af477d3c37538 100644 --- a/lldb/unittests/DAP/TestBase.cpp +++ b/lldb/unittests/DAP/TestBase.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/Error.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" +#include <chrono> #include <cstdio> #include <memory> @@ -37,7 +38,7 @@ using lldb_private::Pipe; void TransportBase::SetUp() { std::tie(to_client, to_server) = TestDAPTransport::createPair(); - log = std::make_unique<Log>(llvm::outs(), log_mutex); + log = std::make_unique<Log>(llvm::errs(), log_mutex); dap = std::make_unique<DAP>( /*log=*/*log, /*default_repl_mode=*/ReplMode::Auto, @@ -56,13 +57,17 @@ void TransportBase::SetUp() { } void TransportBase::Run() { - bool addition_succeeded = loop.AddPendingCallback( - [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); }); + bool addition_succeeded = loop.AddCallback( + [](lldb_private::MainLoopBase &loop) { + llvm::errs() << "Terminating\n"; + loop.RequestTermination(); + }, + std::chrono::seconds(1)); EXPECT_TRUE(addition_succeeded); EXPECT_THAT_ERROR(loop.Run().takeError(), llvm::Succeeded()); } -void DAPTestBase::SetUp() { TransportBase::SetUp(); } +// void DAPTestBase::SetUp() { TransportBase::SetUp(); } void DAPTestBase::TearDown() { if (core) @@ -107,13 +112,9 @@ void DAPTestBase::CreateDebugger() { ASSERT_THAT_ERROR(dap->ConfigureIO(dev_null_stream, dev_null_stream), Succeeded()); - dap->debugger.SetInputFile(dap->in); - auto out_fd = dap->out.GetWriteFileDescriptor(); - ASSERT_THAT_EXPECTED(out_fd, Succeeded()); - dap->debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); - auto err_fd = dap->out.GetWriteFileDescriptor(); - ASSERT_THAT_EXPECTED(err_fd, Succeeded()); - dap->debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); + dap->no_lldbinit = true; + ASSERT_THAT_ERROR(dap->InitializeDebugger(), Succeeded()); + ASSERT_THAT_ERROR(dap->ActivateRepl(), Succeeded()); } void DAPTestBase::LoadCore() { diff --git a/lldb/unittests/DAP/TestBase.h b/lldb/unittests/DAP/TestBase.h index f1c7e6b989729..d8166d04feed2 100644 --- a/lldb/unittests/DAP/TestBase.h +++ b/lldb/unittests/DAP/TestBase.h @@ -110,7 +110,6 @@ class DAPTestBase : public TransportBase { static void SetUpTestSuite(); static void TeatUpTestSuite(); - void SetUp() override; void TearDown() override; bool GetDebuggerSupportsTarget(llvm::StringRef platform); >From 183789c85f77e1adde53e56918c372048ccceddc Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Tue, 10 Feb 2026 15:36:42 -0800 Subject: [PATCH 2/5] Remove testing code. --- .../Python/lldbsuite/test/tools/lldb-dap/dap_server.py | 7 +++---- 1 file changed, 3 insertions(+), 4 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 f3efc7b23214b..acd9b09558eef 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 @@ -486,10 +486,9 @@ def _process_recv_packets(self) -> None: with self._recv_condition: for packet in self._recv_packets: if packet and ("seq" not in packet or packet["seq"] == 0): - pass - # raise ValueError( - # f"received a malformed packet, expected 'seq != 0' for {packet!r}" - # ) + raise ValueError( + f"received a malformed packet, expected 'seq != 0' for {packet!r}" + ) if packet: self._log.messages.append((Log.Dir.RECV, packet)) # Handle events that may modify any stateful properties of >From 8fb124da84c1aeaa9425023150a88dc79d2ae5d1 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Wed, 11 Feb 2026 15:16:24 -0800 Subject: [PATCH 3/5] Correcting the stopped-events tests. --- .../packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py | 2 -- 1 file changed, 2 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 3eafd6d3c0f3b..aeaf5dad3975b 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 @@ -540,8 +540,6 @@ def _handle_event(self, packet: Event) -> None: # reasons since the 'threads' command doesn't return # that information. self._process_stopped() - if "allThreadsStopped" in body and body["allThreadsStopped"]: - self.thread_stop_reasons = {} if "threadId" in body: tid = body["threadId"] self.thread_stop_reasons[tid] = body >From 0c06bef8480dd464e397f4cf2aa8cc7677439d57 Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Wed, 11 Feb 2026 16:22:49 -0800 Subject: [PATCH 4/5] Fixing DAPTests. --- lldb/tools/lldb-dap/DAP.cpp | 24 +++++++++++++----------- lldb/unittests/DAP/TestBase.cpp | 14 ++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 4de0200dabe93..85bb295ff137c 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -297,17 +297,19 @@ llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { std::tie(primary, replica) = *io; - if (auto Error = stdout_redirect.RedirectTo( - overrideOut, [this](llvm::StringRef output) { - SendOutput(OutputType::Console, output); - })) - return Error; - - if (auto Error = stderr_redirect.RedirectTo( - overrideErr, [this](llvm::StringRef output) { - SendOutput(OutputType::Console, output); - })) - return Error; + if (overrideOut) + if (auto Error = stdout_redirect.RedirectTo( + overrideOut, [this](llvm::StringRef output) { + SendOutput(OutputType::Console, output); + })) + return Error; + + if (overrideErr) + if (auto Error = stderr_redirect.RedirectTo( + overrideErr, [this](llvm::StringRef output) { + SendOutput(OutputType::Console, output); + })) + return Error; return llvm::Error::success(); } diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp index c0d6ae7e70aac..61986b74a20b6 100644 --- a/lldb/unittests/DAP/TestBase.cpp +++ b/lldb/unittests/DAP/TestBase.cpp @@ -29,9 +29,6 @@ using namespace lldb; using namespace lldb_dap; using namespace lldb_dap::protocol; using namespace lldb_dap_tests; -using lldb_private::File; -using lldb_private::FileSpec; -using lldb_private::FileSystem; using lldb_private::MainLoop; using lldb_private::Pipe; @@ -98,18 +95,11 @@ void DAPTestBase::CreateDebugger() { ASSERT_TRUE(dap->debugger); dap->target = dap->debugger.GetDummyTarget(); - Expected<lldb::FileUP> dev_null = FileSystem::Instance().Open( - FileSpec(FileSystem::DEV_NULL), File::eOpenOptionReadWrite); - ASSERT_THAT_EXPECTED(dev_null, Succeeded()); - lldb::FileSP dev_null_sp = std::move(*dev_null); - - std::FILE *dev_null_stream = dev_null_sp->GetStream(); - ASSERT_THAT_ERROR(dap->ConfigureIO(dev_null_stream, dev_null_stream), - Succeeded()); + ASSERT_THAT_ERROR(dap->ConfigureIO(), Succeeded()); dap->no_lldbinit = true; ASSERT_THAT_ERROR(dap->InitializeDebugger(), Succeeded()); - ASSERT_THAT_ERROR(dap->ActivateRepl(), Succeeded()); + dap->ActivateRepl(); } void DAPTestBase::LoadCore() { >From 8410caf854366bb638bc6810080bd626da7beaae Mon Sep 17 00:00:00 2001 From: John Harrison <[email protected]> Date: Thu, 12 Feb 2026 09:58:41 -0800 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Ebuka Ezike <[email protected]> --- lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py index 8429941ad574f..4b3e83e3bb57a 100644 --- a/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py +++ b/lldb/test/API/tools/lldb-dap/cancel/TestDAP_cancel.py @@ -36,7 +36,7 @@ def test_pending_request(self): Tests cancelling a pending request. """ program = self.getBuildArtifact("a.out") - busy_loop = os.path.join(os.path.dirname(__file__), "busy_loop.py") + busy_loop = self.getSourcePath("busy_loop.py") self.build_and_launch( program, initCommands=[f"command script import {busy_loop}"], _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
