[Lldb-commits] [lldb] 185d496 - [lldb] Introduce dwim-print command

Dave Lee via lldb-commits lldb-commits at lists.llvm.org
Tue Nov 29 12:46:31 PST 2022


Author: Dave Lee
Date: 2022-11-29T12:46:20-08:00
New Revision: 185d4964a1584cedc3385adb4d6ed863c71f6515

URL: https://github.com/llvm/llvm-project/commit/185d4964a1584cedc3385adb4d6ed863c71f6515
DIFF: https://github.com/llvm/llvm-project/commit/185d4964a1584cedc3385adb4d6ed863c71f6515.diff

LOG: [lldb] Introduce dwim-print command

Implements `dwim-print`, a printing command that chooses the most direct,
efficient, and resilient means of printing a given expression.

DWIM is an acronym for Do What I Mean. From Wikipedia, DWIM is described as:

  > attempt to anticipate what users intend to do, correcting trivial errors
  > automatically rather than blindly executing users' explicit but
  > potentially incorrect input

The `dwim-print` command serves as a single print command for users who don't
yet know, or prefer not to know, the various lldb commands that can be used to
print, and when to use them.

This initial implementation is the base foundation for `dwim-print`. It accepts
no flags, only an expression. If the expression is the name of a variable in
the frame, then effectively `frame variable` is used to get, and print, its
value. Otherwise, printing falls back to using `expression` evaluation. In this
initial version, frame variable paths will be handled with `expression`.

Following this, there are a number of improvements that can be made. Some
improvements include supporting `frame variable` expressions or registers.

To provide transparency, especially as the `dwim-print` command evolves, a new
setting is also introduced: `dwim-print-verbosity`. This setting instructs
`dwim-print` to optionally print a message showing the effective command being
run. For example `dwim-print var.meth()` can print a message such as: "note:
ran `expression var.meth()`".

See https://discourse.llvm.org/t/dwim-print-command/66078 for the proposal and
discussion.

Differential Revision: https://reviews.llvm.org/D138315

Added: 
    lldb/source/Commands/CommandObjectDWIMPrint.cpp
    lldb/source/Commands/CommandObjectDWIMPrint.h
    lldb/test/API/commands/dwim-print/Makefile
    lldb/test/API/commands/dwim-print/TestDWIMPrint.py
    lldb/test/API/commands/dwim-print/main.c

Modified: 
    lldb/include/lldb/Core/Debugger.h
    lldb/include/lldb/lldb-enumerations.h
    lldb/source/Commands/CMakeLists.txt
    lldb/source/Core/CoreProperties.td
    lldb/source/Core/Debugger.cpp
    lldb/source/Interpreter/CommandInterpreter.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 041d1fd86473..c1394c1d0ebd 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -348,6 +348,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
 
   bool SetTabSize(uint32_t tab_size);
 
+  lldb::DWIMPrintVerbosity GetDWIMPrintVerbosity() const;
+
   bool GetEscapeNonPrintables() const;
 
   bool GetNotifyVoid() const;

diff  --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index a5bd557112b3..c46ef4bf3616 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -1203,6 +1203,16 @@ enum TraceCursorSeekType {
   eTraceCursorSeekTypeEnd
 };
 
+/// Enum to control the verbosity level of `dwim-print` execution.
+enum DWIMPrintVerbosity {
+  /// Run `dwim-print` with no verbosity.
+  eDWIMPrintVerbosityNone,
+  /// Print a message when `dwim-print` uses `expression` evaluation.
+  eDWIMPrintVerbosityExpression,
+  /// Always print a message indicating how `dwim-print` is evaluating its
+  /// expression.
+  eDWIMPrintVerbosityFull,
+};
 
 } // namespace lldb
 

diff  --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt
index 0b705ade5ea8..bec6a9c1bc44 100644
--- a/lldb/source/Commands/CMakeLists.txt
+++ b/lldb/source/Commands/CMakeLists.txt
@@ -10,6 +10,7 @@ add_lldb_library(lldbCommands
   CommandObjectCommands.cpp
   CommandObjectDiagnostics.cpp
   CommandObjectDisassemble.cpp
+  CommandObjectDWIMPrint.cpp
   CommandObjectExpression.cpp
   CommandObjectFrame.cpp
   CommandObjectGUI.cpp

diff  --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
new file mode 100644
index 000000000000..232e877f53d7
--- /dev/null
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
@@ -0,0 +1,84 @@
+//===-- CommandObjectDWIMPrint.cpp ------------------------------*- C++ -*-===//
+//
+// 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 "CommandObjectDWIMPrint.h"
+
+#include "lldb/Core/ValueObject.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/CommandObject.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Target/StackFrame.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_private;
+
+CommandObjectDWIMPrint::CommandObjectDWIMPrint(CommandInterpreter &interpreter)
+    : CommandObjectRaw(
+          interpreter, "dwim-print", "Print a variable or expression.",
+          "dwim-print [<variable-name> | <expression>]",
+          eCommandProcessMustBePaused | eCommandTryTargetAPILock |
+              eCommandRequiresFrame | eCommandProcessMustBeLaunched |
+              eCommandRequiresProcess) {}
+
+bool CommandObjectDWIMPrint::DoExecute(StringRef expr,
+                                       CommandReturnObject &result) {
+  // Ignore leading and trailing whitespace.
+  expr = expr.trim();
+
+  if (expr.empty()) {
+    result.AppendErrorWithFormatv("'{0}' takes a variable or expression",
+                                  m_cmd_name);
+    return false;
+  }
+
+  // eCommandRequiresFrame guarantees a frame.
+  StackFrame *frame = m_exe_ctx.GetFramePtr();
+  assert(frame);
+
+  auto verbosity = GetDebugger().GetDWIMPrintVerbosity();
+
+  // First, try `expr` as the name of a variable.
+  {
+    auto valobj_sp = frame->FindVariable(ConstString(expr));
+    if (valobj_sp && valobj_sp->GetError().Success()) {
+      if (verbosity == eDWIMPrintVerbosityFull)
+        result.AppendMessageWithFormatv("note: ran `frame variable {0}`", expr);
+      valobj_sp->Dump(result.GetOutputStream());
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    }
+  }
+
+  // Second, also lastly, try `expr` as a source expression to evaluate.
+  {
+    // eCommandRequiresProcess guarantees a target.
+    Target *target = m_exe_ctx.GetTargetPtr();
+    assert(target);
+
+    ValueObjectSP valobj_sp;
+    if (target->EvaluateExpression(expr, frame, valobj_sp) ==
+        eExpressionCompleted) {
+      if (verbosity != eDWIMPrintVerbosityNone)
+        result.AppendMessageWithFormatv("note: ran `expression -- {0}`", expr);
+      valobj_sp->Dump(result.GetOutputStream());
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+      return true;
+    } else {
+      if (valobj_sp)
+        result.SetError(valobj_sp->GetError());
+      else
+        result.AppendErrorWithFormatv(
+            "unknown error evaluating expression `{0}`", expr);
+      return false;
+    }
+  }
+}

diff  --git a/lldb/source/Commands/CommandObjectDWIMPrint.h b/lldb/source/Commands/CommandObjectDWIMPrint.h
new file mode 100644
index 000000000000..1284949aed3a
--- /dev/null
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.h
@@ -0,0 +1,40 @@
+//===-- CommandObjectDWIMPrint.h --------------------------------*- C++ -*-===//
+//
+// 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_SOURCE_COMMANDS_COMMANDOBJECTDWIMPRINT_H
+#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTDWIMPRINT_H
+
+#include "lldb/Interpreter/CommandObject.h"
+
+namespace lldb_private {
+
+/// Implements `dwim-print`, a printing command that chooses the most direct,
+/// efficient, and resilient means of printing a given expression.
+///
+/// DWIM is an acronym for Do What I Mean. From Wikipedia, DWIM is described as:
+///
+///   > attempt to anticipate what users intend to do, correcting trivial errors
+///   > automatically rather than blindly executing users' explicit but
+///   > potentially incorrect input
+///
+/// The `dwim-print` command serves as a single print command for users who
+/// don't yet know, or perfer not to know, the various lldb commands that can be
+/// used to print, and when to use them.
+class CommandObjectDWIMPrint : public CommandObjectRaw {
+public:
+  CommandObjectDWIMPrint(CommandInterpreter &interpreter);
+
+  ~CommandObjectDWIMPrint() override = default;
+
+private:
+  bool DoExecute(llvm::StringRef command, CommandReturnObject &result) override;
+};
+
+} // namespace lldb_private
+
+#endif

diff  --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index b8b2a881160a..fc664be63f42 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -191,4 +191,9 @@ let Definition = "debugger" in {
     Global,
     DefaultStringValue<"${ansi.normal}">,
     Desc<"When displaying suggestion in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the suggestion.">;
+  def DWIMPrintVerbosity: Property<"dwim-print-verbosity", "Enum">,
+    Global,
+    DefaultEnumValue<"eDWIMPrintVerbosityExpression">,
+    EnumValues<"OptionEnumValues(g_dwim_print_verbosities)">,
+    Desc<"The verbosity level used by dwim-print.">;
 }

diff  --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 77d943787df9..8afd215a1a91 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -52,6 +52,7 @@
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/Utility/StreamString.h"
+#include "lldb/lldb-enumerations.h"
 
 #if defined(_WIN32)
 #include "lldb/Host/windows/PosixApi.h"
@@ -149,6 +150,16 @@ static constexpr OptionEnumValueElement g_language_enumerators[] = {
     },
 };
 
+static constexpr OptionEnumValueElement g_dwim_print_verbosities[] = {
+    {eDWIMPrintVerbosityNone, "none",
+     "Use no verbosity when running dwim-print."},
+    {eDWIMPrintVerbosityExpression, "expression",
+     "Use partial verbosity when running dwim-print - display a message when "
+     "`expression` evaluation is used."},
+    {eDWIMPrintVerbosityFull, "full",
+     "Use full verbosity when running dwim-print."},
+};
+
 static constexpr OptionEnumValueElement s_stop_show_column_values[] = {
     {
         eStopShowColumnAnsiOrCaret,
@@ -520,6 +531,13 @@ bool Debugger::SetTabSize(uint32_t tab_size) {
   return m_collection_sp->SetPropertyAtIndexAsUInt64(nullptr, idx, tab_size);
 }
 
+lldb::DWIMPrintVerbosity Debugger::GetDWIMPrintVerbosity() const {
+  const uint32_t idx = ePropertyDWIMPrintVerbosity;
+  return (lldb::DWIMPrintVerbosity)
+      m_collection_sp->GetPropertyAtIndexAsEnumeration(
+          nullptr, idx, g_debugger_properties[idx].default_uint_value);
+}
+
 #pragma mark Debugger
 
 // const DebuggerPropertiesSP &

diff  --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 3d0b61fa7d3c..ac7bd4bf1fca 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -15,6 +15,7 @@
 #include "Commands/CommandObjectApropos.h"
 #include "Commands/CommandObjectBreakpoint.h"
 #include "Commands/CommandObjectCommands.h"
+#include "Commands/CommandObjectDWIMPrint.h"
 #include "Commands/CommandObjectDiagnostics.h"
 #include "Commands/CommandObjectDisassemble.h"
 #include "Commands/CommandObjectExpression.h"
@@ -532,6 +533,7 @@ void CommandInterpreter::LoadCommandDictionary() {
   REGISTER_COMMAND_OBJECT("command", CommandObjectMultiwordCommands);
   REGISTER_COMMAND_OBJECT("diagnostics", CommandObjectDiagnostics);
   REGISTER_COMMAND_OBJECT("disassemble", CommandObjectDisassemble);
+  REGISTER_COMMAND_OBJECT("dwim-print", CommandObjectDWIMPrint);
   REGISTER_COMMAND_OBJECT("expression", CommandObjectExpression);
   REGISTER_COMMAND_OBJECT("frame", CommandObjectMultiwordFrame);
   REGISTER_COMMAND_OBJECT("gui", CommandObjectGUI);

diff  --git a/lldb/test/API/commands/dwim-print/Makefile b/lldb/test/API/commands/dwim-print/Makefile
new file mode 100644
index 000000000000..10495940055b
--- /dev/null
+++ b/lldb/test/API/commands/dwim-print/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/commands/dwim-print/TestDWIMPrint.py b/lldb/test/API/commands/dwim-print/TestDWIMPrint.py
new file mode 100644
index 000000000000..7bc1448441cc
--- /dev/null
+++ b/lldb/test/API/commands/dwim-print/TestDWIMPrint.py
@@ -0,0 +1,68 @@
+"""
+Test dwim-print with variables, variable paths, and expressions.
+"""
+
+import re
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+import lldbsuite.test.lldbutil as lldbutil
+
+
+class TestCase(TestBase):
+    def setUp(self):
+        TestBase.setUp(self)
+        self.build()
+        lldbutil.run_to_name_breakpoint(self, "main")
+
+    def _run_cmd(self, cmd: str) -> str:
+        """Run the given lldb command and return its output."""
+        result = lldb.SBCommandReturnObject()
+        self.ci.HandleCommand(cmd, result)
+        return result.GetOutput().rstrip()
+
+    PERSISTENT_VAR = re.compile(r"\$\d+")
+
+    def _mask_persistent_var(self, string: str) -> str:
+        """
+        Replace persistent result variables (ex '$0', '$1', etc) with a regex
+        that matches any persistent result (r'\$\d+'). The returned string can
+        be matched against other `expression` results.
+        """
+        before, after = self.PERSISTENT_VAR.split(string, maxsplit=1)
+        return re.escape(before) + r"\$\d+" + re.escape(after)
+
+    def _expect_cmd(self, expr: str, base_cmd: str) -> None:
+        """Run dwim-print and verify the output against the expected command."""
+        cmd = f"{base_cmd} {expr}"
+        cmd_output = self._run_cmd(cmd)
+
+        # Verify dwim-print chose the expected command.
+        self.runCmd("settings set dwim-print-verbosity full")
+        substrs = [f"note: ran `{cmd}`"]
+        patterns = []
+
+        if base_cmd == "expression --" and self.PERSISTENT_VAR.search(cmd_output):
+            patterns.append(self._mask_persistent_var(cmd_output))
+        else:
+            substrs.append(cmd_output)
+
+        self.expect(f"dwim-print {expr}", substrs=substrs, patterns=patterns)
+
+    def test_variables(self):
+        """Test dwim-print with variables."""
+        vars = ("argc", "argv")
+        for var in vars:
+            self._expect_cmd(var, "frame variable")
+
+    def test_variable_paths(self):
+        """Test dwim-print with variable path expressions."""
+        exprs = ("&argc", "*argv", "argv[0]")
+        for expr in exprs:
+            self._expect_cmd(expr, "expression --")
+
+    def test_expressions(self):
+        """Test dwim-print with expressions."""
+        exprs = ("argc + 1", "(void)argc", "(int)abs(argc)")
+        for expr in exprs:
+            self._expect_cmd(expr, "expression --")

diff  --git a/lldb/test/API/commands/dwim-print/main.c b/lldb/test/API/commands/dwim-print/main.c
new file mode 100644
index 000000000000..5c2fa9bb6a78
--- /dev/null
+++ b/lldb/test/API/commands/dwim-print/main.c
@@ -0,0 +1,3 @@
+int main(int argc, char **argv) {
+  return 0;
+}


        


More information about the lldb-commits mailing list