diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -348,6 +348,8 @@ 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 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -1203,6 +1203,16 @@ 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 --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -10,6 +10,7 @@ CommandObjectCommands.cpp CommandObjectDiagnostics.cpp CommandObjectDisassemble.cpp + CommandObjectDWIMPrint.cpp CommandObjectExpression.cpp CommandObjectFrame.cpp CommandObjectGUI.cpp diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.h b/lldb/source/Commands/CommandObjectDWIMPrint.h new file mode 100644 --- /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/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp new file mode 100644 --- /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 [ | ]", + 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/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -191,4 +191,9 @@ 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 --- 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_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 @@ 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 --- 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 @@ 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 --- /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 --- /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 --- /dev/null +++ b/lldb/test/API/commands/dwim-print/main.c @@ -0,0 +1,3 @@ +int main(int argc, char **argv) { + return 0; +}