[Lldb-commits] [lldb] [lldb-dap] Implement quiet commands (PR #74808)

Walter Erquinigo via lldb-commits lldb-commits at lists.llvm.org
Fri Dec 8 08:26:44 PST 2023


https://github.com/walter-erquinigo updated https://github.com/llvm/llvm-project/pull/74808

>From d544ba0cc6836c0b5c2086edccfc2b41080174ef Mon Sep 17 00:00:00 2001
From: walter erquinigo <walter at modular.com>
Date: Fri, 8 Dec 2023 00:11:19 -0500
Subject: [PATCH] [lldb-dap] Implement quiet commands

This adds support for optionally prefixing any command with `?`, which effectively prevents the output of these commands to be printed to the console unless they fail. This comes handy when programmaticaly running commands on behalf of the user without wanting them to know unless they fail.

In a later PR I plan to implement the `!` prefix for commands that abort lldb-dap upon errors.
---
 .../test/API/tools/lldb-dap/commands/Makefile |  3 +
 .../lldb-dap/commands/TestDAP_commands.py     | 44 ++++++++++++++
 .../test/API/tools/lldb-dap/commands/main.cpp |  1 +
 lldb/tools/lldb-dap/LLDBUtils.cpp             | 59 ++++++++++++++++---
 lldb/tools/lldb-dap/LLDBUtils.h               | 20 ++++++-
 lldb/tools/lldb-dap/lldb-dap.cpp              |  3 +-
 lldb/tools/lldb-dap/package.json              | 24 ++++----
 7 files changed, 130 insertions(+), 24 deletions(-)
 create mode 100644 lldb/test/API/tools/lldb-dap/commands/Makefile
 create mode 100644 lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py
 create mode 100644 lldb/test/API/tools/lldb-dap/commands/main.cpp

diff --git a/lldb/test/API/tools/lldb-dap/commands/Makefile b/lldb/test/API/tools/lldb-dap/commands/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/commands/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py b/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py
new file mode 100644
index 0000000000000..aa0c76b056a46
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/commands/TestDAP_commands.py
@@ -0,0 +1,44 @@
+import os
+
+import dap_server
+import lldbdap_testcase
+from lldbsuite.test import lldbtest, lldbutil
+from lldbsuite.test.decorators import *
+
+
+class TestDAP_commands(lldbdap_testcase.DAPTestCaseBase):
+    def test_command_directive_quiet_on_success(self):
+        program = self.getBuildArtifact("a.out")
+        command_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes false"
+        )
+        command_not_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes true"
+        )
+        self.build_and_launch(
+            program,
+            initCommands=["?" + command_quiet, command_not_quiet],
+        )
+        full_output = self.collect_console(duration=1.0)
+        self.assertNotIn(command_quiet, full_output)
+        self.assertIn(command_not_quiet, full_output)
+
+    def test_command_directive_abort_on_error(self):
+        program = self.getBuildArtifact("a.out")
+        command_quiet = (
+            "settings set target.show-hex-variable-values-with-leading-zeroes false"
+        )
+        command_abort_on_error = "settings set foo bar"
+        try:
+            # We use try/except because the client raises after the adapter
+            # abruptly terminates.
+            self.build_and_launch(
+                program,
+                initCommands=["?!" + command_quiet, "!" + command_abort_on_error],
+            )
+        except:
+            pass
+        full_output = self.collect_console(duration=1.0)
+        self.assertNotIn(command_quiet, full_output)
+        self.assertIn(command_abort_on_error, full_output)
+        self.assertIn("Terminating", full_output)
diff --git a/lldb/test/API/tools/lldb-dap/commands/main.cpp b/lldb/test/API/tools/lldb-dap/commands/main.cpp
new file mode 100644
index 0000000000000..76e8197013aab
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/commands/main.cpp
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp
index 955c11f636895..2c127f162dc56 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.cpp
+++ b/lldb/tools/lldb-dap/LLDBUtils.cpp
@@ -13,16 +13,22 @@ namespace lldb_dap {
 
 void RunLLDBCommands(llvm::StringRef prefix,
                      const llvm::ArrayRef<std::string> &commands,
-                     llvm::raw_ostream &strm) {
+                     llvm::raw_ostream &strm, bool parse_command_directives) {
   if (commands.empty())
     return;
-  lldb::SBCommandInterpreter interp = g_dap.debugger.GetCommandInterpreter();
-  if (!prefix.empty())
-    strm << prefix << "\n";
-  for (const auto &command : commands) {
-    lldb::SBCommandReturnObject result;
+
+  bool did_print_prefix = false;
+
+  auto print_result = [&](llvm::StringRef command,
+                          lldb::SBCommandReturnObject &result,
+                          llvm::raw_ostream &strm) {
+    // We only print the prefix if we are effectively printing at least one
+    // command.
+    if (!did_print_prefix && !prefix.empty()) {
+      strm << prefix << "\n";
+      did_print_prefix = true;
+    }
     strm << "(lldb) " << command << "\n";
-    interp.HandleCommand(command.c_str(), result);
     auto output_len = result.GetOutputSize();
     if (output_len) {
       const char *output = result.GetOutput();
@@ -33,14 +39,49 @@ void RunLLDBCommands(llvm::StringRef prefix,
       const char *error = result.GetError();
       strm << error;
     }
+  };
+
+  lldb::SBCommandInterpreter interp = g_dap.debugger.GetCommandInterpreter();
+  for (llvm::StringRef command : commands) {
+    lldb::SBCommandReturnObject result;
+    bool quiet_on_success = false;
+    bool abort_on_error = false;
+
+    while (parse_command_directives) {
+      if (command.starts_with("?")) {
+        command = command.drop_front();
+        quiet_on_success = true;
+      } else if (command.starts_with("!")) {
+        command = command.drop_front();
+        abort_on_error = true;
+      } else {
+        break;
+      }
+    }
+
+    interp.HandleCommand(command.str().c_str(), result);
+
+    if (!result.Succeeded() && abort_on_error) {
+      std::string output;
+      llvm::raw_string_ostream os(output);
+      print_result(command, result, os);
+      os << "\nTerminating the debug session due to the critical failure of "
+            "the last executed command...\n";
+      g_dap.SendOutput(OutputType::Console, output);
+      std::abort();
+    }
+
+    if (!(result.Succeeded() && quiet_on_success))
+      print_result(command, result, strm);
   }
 }
 
 std::string RunLLDBCommands(llvm::StringRef prefix,
-                            const llvm::ArrayRef<std::string> &commands) {
+                            const llvm::ArrayRef<std::string> &commands,
+                            bool parse_command_directives) {
   std::string s;
   llvm::raw_string_ostream strm(s);
-  RunLLDBCommands(prefix, commands, strm);
+  RunLLDBCommands(prefix, commands, strm, parse_command_directives);
   strm.flush();
   return s;
 }
diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h
index a99f798835370..7eef7d013040b 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.h
+++ b/lldb/tools/lldb-dap/LLDBUtils.h
@@ -23,6 +23,12 @@ namespace lldb_dap {
 /// All output from every command, including the prompt + the command
 /// is placed into the "strm" argument.
 ///
+/// Each individual command can be prefixed with \b ! and/or \b ? in no
+/// particular order. If \b ? is provided, then the output of that command is
+/// only emitted if it fails, and if \b ! is provided, then this program aborts
+/// if the command fails, in which case the output of that command is emitted
+/// to the Debug Console regardless.
+///
 /// \param[in] prefix
 ///     A string that will be printed into \a strm prior to emitting
 ///     the prompt + command and command output. Can be NULL.
@@ -33,9 +39,14 @@ namespace lldb_dap {
 /// \param[in] strm
 ///     The stream that will receive the prefix, prompt + command and
 ///     all command output.
+///
+/// \param[in] parse_command_directives
+///     If \b false, then command prefixes like \b ! or \b ? are not parsed and
+///     each command is executed verbatim.
 void RunLLDBCommands(llvm::StringRef prefix,
                      const llvm::ArrayRef<std::string> &commands,
-                     llvm::raw_ostream &strm);
+                     llvm::raw_ostream &strm,
+                     bool parse_command_directives = true);
 
 /// Run a list of LLDB commands in the LLDB command interpreter.
 ///
@@ -49,11 +60,16 @@ void RunLLDBCommands(llvm::StringRef prefix,
 /// \param[in] commands
 ///     An array of LLDB commands to execute.
 ///
+/// \param[in] parse_command_directives
+///     If \b false, then command prefixes like \b ! or \b ? are not parsed and
+///     each command is executed verbatim.
+//
 /// \return
 ///     A std::string that contains the prefix and all commands and
 ///     command output
 std::string RunLLDBCommands(llvm::StringRef prefix,
-                            const llvm::ArrayRef<std::string> &commands);
+                            const llvm::ArrayRef<std::string> &commands,
+                            bool parse_command_directives = true);
 
 /// Check if a thread has a stop reason.
 ///
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index d6b593eba93ec..4210a9c94871a 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -1270,7 +1270,8 @@ void request_evaluate(const llvm::json::Object &request) {
     if (frame.IsValid()) {
       g_dap.focus_tid = frame.GetThread().GetThreadID();
     }
-    auto result = RunLLDBCommands(llvm::StringRef(), {std::string(expression)});
+    auto result = RunLLDBCommands(llvm::StringRef(), {std::string(expression)},
+                                  /*parse_command_directives=*/false);
     EmplaceSafeString(body, "result", result);
     body.try_emplace("variablesReference", (int64_t)0);
   } else {
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index ebb1103d695e1..2e11933b02955 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -204,32 +204,32 @@
 							},
 							"initCommands": {
 								"type": "array",
-								"description": "Initialization commands executed upon debugger startup.",
+								"description": "Initialization commands executed upon debugger startup. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"preRunCommands": {
 								"type": "array",
-								"description": "Commands executed just before the program is launched.",
+								"description": "Commands executed just before the program is launched. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"postRunCommands": {
 								"type": "array",
-								"description": "Commands executed just as soon as the program is successfully launched when it's in a stopped state prior to any automatic continuation.",
+								"description": "Commands executed just as soon as the program is successfully launched when it's in a stopped state prior to any automatic continuation. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"launchCommands": {
 								"type": "array",
-								"description": "Custom commands that are executed instead of launching a process. A target will be created with the launch arguments prior to executing these commands. The commands may optionally create a new target and must perform a launch. A valid process must exist after these commands complete or the \"launch\" will fail. Launch the process with \"process launch -s\" to make the process to at the entry point since lldb-dap will auto resume if necessary.",
+								"description": "Custom commands that are executed instead of launching a process. A target will be created with the launch arguments prior to executing these commands. The commands may optionally create a new target and must perform a launch. A valid process must exist after these commands complete or the \"launch\" will fail. Launch the process with \"process launch -s\" to make the process to at the entry point since lldb-dap will auto resume if necessary. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"stopCommands": {
 								"type": "array",
-								"description": "Commands executed each time the program stops.",
+								"description": "Commands executed each time the program stops. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"exitCommands": {
 								"type": "array",
-								"description": "Commands executed at the end of debugging session.",
+								"description": "Commands executed at the end of debugging session. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"runInTerminal": {
@@ -309,32 +309,32 @@
 							},
 							"attachCommands": {
 								"type": "array",
-								"description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail.",
+								"description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"initCommands": {
 								"type": "array",
-								"description": "Initialization commands executed upon debugger startup.",
+								"description": "Initialization commands executed upon debugger startup. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"preRunCommands": {
 								"type": "array",
-								"description": "Commands executed just before the program is attached to.",
+								"description": "Commands executed just before the program is attached to. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"postRunCommands": {
 								"type": "array",
-								"description": "Commands executed just as soon as the program is successfully attached when it's in a stopped state prior to any automatic continuation.",
+								"description": "Commands executed just as soon as the program is successfully attached when it's in a stopped state prior to any automatic continuation. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"stopCommands": {
 								"type": "array",
-								"description": "Commands executed each time the program stops.",
+								"description": "Commands executed each time the program stops. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"exitCommands": {
 								"type": "array",
-								"description": "Commands executed at the end of debugging session.",
+								"description": "Commands executed at the end of debugging session. Each command can be prefixed with `!` and/or `?` in no particular order. If `?` is provided, then the output of the command is only emitted if it fails, and if `!` is provided, the debug session terminates if the command fails, in which case the output of the command is emitted regardless.",
 								"default": []
 							},
 							"coreFile": {



More information about the lldb-commits mailing list