Skip to content

Commit ebe2dcf

Browse files
committed
[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 (cherry picked from commit 185d496)
1 parent eca732f commit ebe2dcf

File tree

11 files changed

+236
-0
lines changed

11 files changed

+236
-0
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
352352

353353
bool SetTabSize(uint32_t tab_size);
354354

355+
lldb::DWIMPrintVerbosity GetDWIMPrintVerbosity() const;
356+
355357
bool GetEscapeNonPrintables() const;
356358

357359
bool GetNotifyVoid() const;

lldb/include/lldb/lldb-enumerations.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,6 +1234,16 @@ enum TraceCursorSeekType {
12341234
eTraceCursorSeekTypeEnd
12351235
};
12361236

1237+
/// Enum to control the verbosity level of `dwim-print` execution.
1238+
enum DWIMPrintVerbosity {
1239+
/// Run `dwim-print` with no verbosity.
1240+
eDWIMPrintVerbosityNone,
1241+
/// Print a message when `dwim-print` uses `expression` evaluation.
1242+
eDWIMPrintVerbosityExpression,
1243+
/// Always print a message indicating how `dwim-print` is evaluating its
1244+
/// expression.
1245+
eDWIMPrintVerbosityFull,
1246+
};
12371247

12381248
} // namespace lldb
12391249

lldb/source/Commands/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_lldb_library(lldbCommands
1414
CommandObjectBreakpointCommand.cpp
1515
CommandObjectCommands.cpp
1616
CommandObjectDisassemble.cpp
17+
CommandObjectDWIMPrint.cpp
1718
CommandObjectExpression.cpp
1819
CommandObjectFrame.cpp
1920
CommandObjectGUI.cpp
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//===-- CommandObjectDWIMPrint.cpp ------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "CommandObjectDWIMPrint.h"
10+
11+
#include "lldb/Core/ValueObject.h"
12+
#include "lldb/Interpreter/CommandInterpreter.h"
13+
#include "lldb/Interpreter/CommandObject.h"
14+
#include "lldb/Interpreter/CommandReturnObject.h"
15+
#include "lldb/Target/StackFrame.h"
16+
#include "lldb/Utility/ConstString.h"
17+
#include "lldb/lldb-enumerations.h"
18+
#include "lldb/lldb-forward.h"
19+
20+
using namespace llvm;
21+
using namespace lldb;
22+
using namespace lldb_private;
23+
24+
CommandObjectDWIMPrint::CommandObjectDWIMPrint(CommandInterpreter &interpreter)
25+
: CommandObjectRaw(
26+
interpreter, "dwim-print", "Print a variable or expression.",
27+
"dwim-print [<variable-name> | <expression>]",
28+
eCommandProcessMustBePaused | eCommandTryTargetAPILock |
29+
eCommandRequiresFrame | eCommandProcessMustBeLaunched |
30+
eCommandRequiresProcess) {}
31+
32+
bool CommandObjectDWIMPrint::DoExecute(StringRef expr,
33+
CommandReturnObject &result) {
34+
// Ignore leading and trailing whitespace.
35+
expr = expr.trim();
36+
37+
if (expr.empty()) {
38+
result.AppendErrorWithFormatv("'{0}' takes a variable or expression",
39+
m_cmd_name);
40+
return false;
41+
}
42+
43+
// eCommandRequiresFrame guarantees a frame.
44+
StackFrame *frame = m_exe_ctx.GetFramePtr();
45+
assert(frame);
46+
47+
auto verbosity = GetDebugger().GetDWIMPrintVerbosity();
48+
49+
// First, try `expr` as the name of a variable.
50+
{
51+
auto valobj_sp = frame->FindVariable(ConstString(expr));
52+
if (valobj_sp && valobj_sp->GetError().Success()) {
53+
if (verbosity == eDWIMPrintVerbosityFull)
54+
result.AppendMessageWithFormatv("note: ran `frame variable {0}`", expr);
55+
valobj_sp->Dump(result.GetOutputStream());
56+
result.SetStatus(eReturnStatusSuccessFinishResult);
57+
return true;
58+
}
59+
}
60+
61+
// Second, also lastly, try `expr` as a source expression to evaluate.
62+
{
63+
// eCommandRequiresProcess guarantees a target.
64+
Target *target = m_exe_ctx.GetTargetPtr();
65+
assert(target);
66+
67+
ValueObjectSP valobj_sp;
68+
if (target->EvaluateExpression(expr, frame, valobj_sp) ==
69+
eExpressionCompleted) {
70+
if (verbosity != eDWIMPrintVerbosityNone)
71+
result.AppendMessageWithFormatv("note: ran `expression -- {0}`", expr);
72+
valobj_sp->Dump(result.GetOutputStream());
73+
result.SetStatus(eReturnStatusSuccessFinishResult);
74+
return true;
75+
} else {
76+
if (valobj_sp)
77+
result.SetError(valobj_sp->GetError());
78+
else
79+
result.AppendErrorWithFormatv(
80+
"unknown error evaluating expression `{0}`", expr);
81+
return false;
82+
}
83+
}
84+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===-- CommandObjectDWIMPrint.h --------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTDWIMPRINT_H
10+
#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTDWIMPRINT_H
11+
12+
#include "lldb/Interpreter/CommandObject.h"
13+
14+
namespace lldb_private {
15+
16+
/// Implements `dwim-print`, a printing command that chooses the most direct,
17+
/// efficient, and resilient means of printing a given expression.
18+
///
19+
/// DWIM is an acronym for Do What I Mean. From Wikipedia, DWIM is described as:
20+
///
21+
/// > attempt to anticipate what users intend to do, correcting trivial errors
22+
/// > automatically rather than blindly executing users' explicit but
23+
/// > potentially incorrect input
24+
///
25+
/// The `dwim-print` command serves as a single print command for users who
26+
/// don't yet know, or perfer not to know, the various lldb commands that can be
27+
/// used to print, and when to use them.
28+
class CommandObjectDWIMPrint : public CommandObjectRaw {
29+
public:
30+
CommandObjectDWIMPrint(CommandInterpreter &interpreter);
31+
32+
~CommandObjectDWIMPrint() override = default;
33+
34+
private:
35+
bool DoExecute(llvm::StringRef command, CommandReturnObject &result) override;
36+
};
37+
38+
} // namespace lldb_private
39+
40+
#endif

lldb/source/Core/CoreProperties.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,9 @@ let Definition = "debugger" in {
225225
Global,
226226
DefaultStringValue<"${ansi.normal}">,
227227
Desc<"When displaying suggestion in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the suggestion.">;
228+
def DWIMPrintVerbosity: Property<"dwim-print-verbosity", "Enum">,
229+
Global,
230+
DefaultEnumValue<"eDWIMPrintVerbosityExpression">,
231+
EnumValues<"OptionEnumValues(g_dwim_print_verbosities)">,
232+
Desc<"The verbosity level used by dwim-print.">;
228233
}

lldb/source/Core/Debugger.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "lldb/Utility/State.h"
5252
#include "lldb/Utility/Stream.h"
5353
#include "lldb/Utility/StreamString.h"
54+
#include "lldb/lldb-enumerations.h"
5455

5556
#if defined(_WIN32)
5657
#include "lldb/Host/windows/PosixApi.h"
@@ -148,6 +149,16 @@ static constexpr OptionEnumValueElement g_language_enumerators[] = {
148149
},
149150
};
150151

152+
static constexpr OptionEnumValueElement g_dwim_print_verbosities[] = {
153+
{eDWIMPrintVerbosityNone, "none",
154+
"Use no verbosity when running dwim-print."},
155+
{eDWIMPrintVerbosityExpression, "expression",
156+
"Use partial verbosity when running dwim-print - display a message when "
157+
"`expression` evaluation is used."},
158+
{eDWIMPrintVerbosityFull, "full",
159+
"Use full verbosity when running dwim-print."},
160+
};
161+
151162
static constexpr OptionEnumValueElement s_stop_show_column_values[] = {
152163
{
153164
eStopShowColumnAnsiOrCaret,
@@ -519,6 +530,13 @@ bool Debugger::SetTabSize(uint32_t tab_size) {
519530
return m_collection_sp->SetPropertyAtIndexAsUInt64(nullptr, idx, tab_size);
520531
}
521532

533+
lldb::DWIMPrintVerbosity Debugger::GetDWIMPrintVerbosity() const {
534+
const uint32_t idx = ePropertyDWIMPrintVerbosity;
535+
return (lldb::DWIMPrintVerbosity)
536+
m_collection_sp->GetPropertyAtIndexAsEnumeration(
537+
nullptr, idx, g_debugger_properties[idx].default_uint_value);
538+
}
539+
522540
#pragma mark Debugger
523541

524542
// const DebuggerPropertiesSP &

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "Commands/CommandObjectApropos.h"
1616
#include "Commands/CommandObjectBreakpoint.h"
1717
#include "Commands/CommandObjectCommands.h"
18+
#include "Commands/CommandObjectDWIMPrint.h"
1819
#include "Commands/CommandObjectDisassemble.h"
1920
#include "Commands/CommandObjectExpression.h"
2021
#include "Commands/CommandObjectFrame.h"
@@ -538,6 +539,7 @@ void CommandInterpreter::LoadCommandDictionary() {
538539
REGISTER_COMMAND_OBJECT("breakpoint", CommandObjectMultiwordBreakpoint);
539540
REGISTER_COMMAND_OBJECT("command", CommandObjectMultiwordCommands);
540541
REGISTER_COMMAND_OBJECT("disassemble", CommandObjectDisassemble);
542+
REGISTER_COMMAND_OBJECT("dwim-print", CommandObjectDWIMPrint);
541543
REGISTER_COMMAND_OBJECT("expression", CommandObjectExpression);
542544
REGISTER_COMMAND_OBJECT("frame", CommandObjectMultiwordFrame);
543545
REGISTER_COMMAND_OBJECT("gui", CommandObjectGUI);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Test dwim-print with variables, variable paths, and expressions.
3+
"""
4+
5+
import re
6+
import lldb
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test.decorators import *
9+
import lldbsuite.test.lldbutil as lldbutil
10+
11+
12+
class TestCase(TestBase):
13+
def setUp(self):
14+
TestBase.setUp(self)
15+
self.build()
16+
lldbutil.run_to_name_breakpoint(self, "main")
17+
18+
def _run_cmd(self, cmd: str) -> str:
19+
"""Run the given lldb command and return its output."""
20+
result = lldb.SBCommandReturnObject()
21+
self.ci.HandleCommand(cmd, result)
22+
return result.GetOutput().rstrip()
23+
24+
PERSISTENT_VAR = re.compile(r"\$\d+")
25+
26+
def _mask_persistent_var(self, string: str) -> str:
27+
"""
28+
Replace persistent result variables (ex '$0', '$1', etc) with a regex
29+
that matches any persistent result (r'\$\d+'). The returned string can
30+
be matched against other `expression` results.
31+
"""
32+
before, after = self.PERSISTENT_VAR.split(string, maxsplit=1)
33+
return re.escape(before) + r"\$\d+" + re.escape(after)
34+
35+
def _expect_cmd(self, expr: str, base_cmd: str) -> None:
36+
"""Run dwim-print and verify the output against the expected command."""
37+
cmd = f"{base_cmd} {expr}"
38+
cmd_output = self._run_cmd(cmd)
39+
40+
# Verify dwim-print chose the expected command.
41+
self.runCmd("settings set dwim-print-verbosity full")
42+
substrs = [f"note: ran `{cmd}`"]
43+
patterns = []
44+
45+
if base_cmd == "expression --" and self.PERSISTENT_VAR.search(cmd_output):
46+
patterns.append(self._mask_persistent_var(cmd_output))
47+
else:
48+
substrs.append(cmd_output)
49+
50+
self.expect(f"dwim-print {expr}", substrs=substrs, patterns=patterns)
51+
52+
def test_variables(self):
53+
"""Test dwim-print with variables."""
54+
vars = ("argc", "argv")
55+
for var in vars:
56+
self._expect_cmd(var, "frame variable")
57+
58+
def test_variable_paths(self):
59+
"""Test dwim-print with variable path expressions."""
60+
exprs = ("&argc", "*argv", "argv[0]")
61+
for expr in exprs:
62+
self._expect_cmd(expr, "expression --")
63+
64+
def test_expressions(self):
65+
"""Test dwim-print with expressions."""
66+
exprs = ("argc + 1", "(void)argc", "(int)abs(argc)")
67+
for expr in exprs:
68+
self._expect_cmd(expr, "expression --")

0 commit comments

Comments
 (0)