Skip to content

Commit 86068dd

Browse files
authored
Merge pull request #6274 from apple/dl/cherry-pick-dwim-print
[lldb] Cherry-pick initial dwim-print command commits
2 parents d7c078f + 7f026ca commit 86068dd

File tree

11 files changed

+310
-0
lines changed

11 files changed

+310
-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: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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/DataFormatters/DumpValueObjectOptions.h"
13+
#include "lldb/Interpreter/CommandInterpreter.h"
14+
#include "lldb/Interpreter/CommandObject.h"
15+
#include "lldb/Interpreter/CommandReturnObject.h"
16+
#include "lldb/Interpreter/OptionGroupFormat.h"
17+
#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h"
18+
#include "lldb/Target/StackFrame.h"
19+
#include "lldb/Utility/ConstString.h"
20+
#include "lldb/lldb-defines.h"
21+
#include "lldb/lldb-enumerations.h"
22+
#include "lldb/lldb-forward.h"
23+
#include "llvm/ADT/StringRef.h"
24+
#include "llvm/Support/FormatVariadic.h"
25+
26+
using namespace llvm;
27+
using namespace lldb;
28+
using namespace lldb_private;
29+
30+
CommandObjectDWIMPrint::CommandObjectDWIMPrint(CommandInterpreter &interpreter)
31+
: CommandObjectRaw(interpreter, "dwim-print",
32+
"Print a variable or expression.",
33+
"dwim-print [<variable-name> | <expression>]",
34+
eCommandProcessMustBePaused | eCommandTryTargetAPILock) {
35+
m_option_group.Append(&m_format_options,
36+
OptionGroupFormat::OPTION_GROUP_FORMAT |
37+
OptionGroupFormat::OPTION_GROUP_GDB_FMT,
38+
LLDB_OPT_SET_1);
39+
m_option_group.Append(&m_varobj_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1);
40+
m_option_group.Finalize();
41+
}
42+
43+
Options *CommandObjectDWIMPrint::GetOptions() { return &m_option_group; }
44+
45+
bool CommandObjectDWIMPrint::DoExecute(StringRef command,
46+
CommandReturnObject &result) {
47+
m_option_group.NotifyOptionParsingStarting(&m_exe_ctx);
48+
OptionsWithRaw args{command};
49+
StringRef expr = args.GetRawPart();
50+
51+
if (args.HasArgs()) {
52+
if (!ParseOptionsAndNotify(args.GetArgs(), result, m_option_group,
53+
m_exe_ctx))
54+
return false;
55+
} else if (command.empty()) {
56+
result.AppendErrorWithFormatv("'{0}' takes a variable or expression",
57+
m_cmd_name);
58+
return false;
59+
}
60+
auto verbosity = GetDebugger().GetDWIMPrintVerbosity();
61+
62+
DumpValueObjectOptions dump_options = m_varobj_options.GetAsDumpOptions(
63+
eLanguageRuntimeDescriptionDisplayVerbosityFull,
64+
m_format_options.GetFormat());
65+
66+
// First, try `expr` as the name of a frame variable.
67+
if (StackFrame *frame = m_exe_ctx.GetFramePtr()) {
68+
auto valobj_sp = frame->FindVariable(ConstString(expr));
69+
if (valobj_sp && valobj_sp->GetError().Success()) {
70+
if (verbosity == eDWIMPrintVerbosityFull) {
71+
StringRef flags;
72+
if (args.HasArgs())
73+
flags = args.GetArgString();
74+
result.AppendMessageWithFormatv("note: ran `frame variable {0}{1}`",
75+
flags, expr);
76+
}
77+
valobj_sp->Dump(result.GetOutputStream(), dump_options);
78+
result.SetStatus(eReturnStatusSuccessFinishResult);
79+
return true;
80+
}
81+
}
82+
83+
// Second, also lastly, try `expr` as a source expression to evaluate.
84+
{
85+
Target *target_ptr = m_exe_ctx.GetTargetPtr();
86+
// Fallback to the dummy target, which can allow for expression evaluation.
87+
Target &target = target_ptr ? *target_ptr : GetDummyTarget();
88+
89+
auto *exe_scope = m_exe_ctx.GetBestExecutionContextScope();
90+
ValueObjectSP valobj_sp;
91+
if (target.EvaluateExpression(expr, exe_scope, valobj_sp) ==
92+
eExpressionCompleted) {
93+
if (verbosity != eDWIMPrintVerbosityNone) {
94+
StringRef flags;
95+
if (args.HasArgs())
96+
flags = args.GetArgStringWithDelimiter();
97+
result.AppendMessageWithFormatv("note: ran `expression {0}{1}`", flags,
98+
expr);
99+
}
100+
valobj_sp->Dump(result.GetOutputStream(), dump_options);
101+
result.SetStatus(eReturnStatusSuccessFinishResult);
102+
return true;
103+
} else {
104+
if (valobj_sp)
105+
result.SetError(valobj_sp->GetError());
106+
else
107+
result.AppendErrorWithFormatv(
108+
"unknown error evaluating expression `{0}`", expr);
109+
return false;
110+
}
111+
}
112+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
#include "lldb/Interpreter/OptionGroupFormat.h"
14+
#include "lldb/Interpreter/OptionGroupValueObjectDisplay.h"
15+
#include "lldb/Interpreter/OptionValueFormat.h"
16+
17+
namespace lldb_private {
18+
19+
/// Implements `dwim-print`, a printing command that chooses the most direct,
20+
/// efficient, and resilient means of printing a given expression.
21+
///
22+
/// DWIM is an acronym for Do What I Mean. From Wikipedia, DWIM is described as:
23+
///
24+
/// > attempt to anticipate what users intend to do, correcting trivial errors
25+
/// > automatically rather than blindly executing users' explicit but
26+
/// > potentially incorrect input
27+
///
28+
/// The `dwim-print` command serves as a single print command for users who
29+
/// don't yet know, or perfer not to know, the various lldb commands that can be
30+
/// used to print, and when to use them.
31+
class CommandObjectDWIMPrint : public CommandObjectRaw {
32+
public:
33+
CommandObjectDWIMPrint(CommandInterpreter &interpreter);
34+
35+
~CommandObjectDWIMPrint() override = default;
36+
37+
Options *GetOptions() override;
38+
39+
private:
40+
bool DoExecute(llvm::StringRef command, CommandReturnObject &result) override;
41+
42+
OptionGroupOptions m_option_group;
43+
OptionGroupFormat m_format_options = lldb::eFormatDefault;
44+
OptionGroupValueObjectDisplay m_varobj_options;
45+
};
46+
47+
} // namespace lldb_private
48+
49+
#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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 _run_cmd(self, cmd: str) -> str:
14+
"""Run the given lldb command and return its output."""
15+
result = lldb.SBCommandReturnObject()
16+
self.ci.HandleCommand(cmd, result)
17+
return result.GetOutput().rstrip()
18+
19+
PERSISTENT_VAR = re.compile(r"\$\d+")
20+
21+
def _mask_persistent_var(self, string: str) -> str:
22+
"""
23+
Replace persistent result variables (ex '$0', '$1', etc) with a regex
24+
that matches any persistent result (r'\$\d+'). The returned string can
25+
be matched against other `expression` results.
26+
"""
27+
before, after = self.PERSISTENT_VAR.split(string, maxsplit=1)
28+
return re.escape(before) + r"\$\d+" + re.escape(after)
29+
30+
def _expect_cmd(
31+
self,
32+
dwim_cmd: str,
33+
actual_cmd: str,
34+
) -> None:
35+
"""Run dwim-print and verify the output against the expected command."""
36+
# Resolve the dwim-print command to either `expression` or `frame variable`.
37+
substitute_cmd = dwim_cmd.replace("dwim-print", actual_cmd, 1)
38+
interp = self.dbg.GetCommandInterpreter()
39+
result = lldb.SBCommandReturnObject()
40+
interp.ResolveCommand(substitute_cmd, result)
41+
self.assertTrue(result.Succeeded(), result.GetError())
42+
43+
resolved_cmd = result.GetOutput()
44+
if actual_cmd == "frame variable":
45+
resolved_cmd = resolved_cmd.replace(" -- ", " ", 1)
46+
47+
expected_output = self._run_cmd(resolved_cmd)
48+
49+
# Verify dwim-print chose the expected command.
50+
self.runCmd("settings set dwim-print-verbosity full")
51+
substrs = [f"note: ran `{resolved_cmd}`"]
52+
patterns = []
53+
54+
if actual_cmd == "expression" and self.PERSISTENT_VAR.search(expected_output):
55+
patterns.append(self._mask_persistent_var(expected_output))
56+
else:
57+
substrs.append(expected_output)
58+
59+
self.expect(dwim_cmd, substrs=substrs, patterns=patterns)
60+
61+
def test_variables(self):
62+
"""Test dwim-print with variables."""
63+
self.build()
64+
lldbutil.run_to_name_breakpoint(self, "main")
65+
vars = ("argc", "argv")
66+
for var in vars:
67+
self._expect_cmd(f"dwim-print {var}", "frame variable")
68+
69+
def test_variable_paths(self):
70+
"""Test dwim-print with variable path expressions."""
71+
self.build()
72+
lldbutil.run_to_name_breakpoint(self, "main")
73+
exprs = ("&argc", "*argv", "argv[0]")
74+
for expr in exprs:
75+
self._expect_cmd(f"dwim-print {expr}", "expression")
76+
77+
def test_expressions(self):
78+
"""Test dwim-print with expressions."""
79+
self.build()
80+
lldbutil.run_to_name_breakpoint(self, "main")
81+
exprs = ("argc + 1", "(void)argc", "(int)abs(argc)")
82+
for expr in exprs:
83+
self._expect_cmd(f"dwim-print {expr}", "expression")
84+
85+
def test_dummy_target_expressions(self):
86+
"""Test dwim-print's ability to evaluate expressions without a target."""
87+
self._expect_cmd("dwim-print 1 + 2", "expression")
88+
89+
def test_gdb_format(self):
90+
self.build()
91+
lldbutil.run_to_name_breakpoint(self, "main")
92+
self._expect_cmd(f"dwim-print/x argc", "frame variable")
93+
self._expect_cmd(f"dwim-print/x argc + 1", "expression")
94+
95+
def test_format_flags(self):
96+
self.build()
97+
lldbutil.run_to_name_breakpoint(self, "main")
98+
self._expect_cmd(f"dwim-print -fx -- argc", "frame variable")
99+
self._expect_cmd(f"dwim-print -fx -- argc + 1", "expression")
100+
101+
def test_display_flags(self):
102+
self.build()
103+
lldbutil.run_to_name_breakpoint(self, "main")
104+
self._expect_cmd(f"dwim-print -T -- argc", "frame variable")
105+
self._expect_cmd(f"dwim-print -T -- argc + 1", "expression")

0 commit comments

Comments
 (0)