Skip to content

Commit 63c77bf

Browse files
committed
[lldb] Make persisting result variables configurable
Context: The `expression` command uses artificial variables to store the expression result. This result variable is unconditionally kept around after the expression command has completed. These variables are known as persistent results. These are the variables `$0`, `$1`, etc, that are displayed when running `p` or `expression`. This change allows users to control whether result variables are persisted, by introducing a `--persistent-result` flag. This change keeps the current default behavior, persistent results are created by default. This change gives users the ability to opt-out by re-aliasing `p`. For example: ``` command unalias p command alias p expression --persistent-result false -- ``` For consistency, this flag is also adopted by `dwim-print`. Of note, if asked, `dwim-print` will create a persistent result even for frame variables. Differential Revision: https://reviews.llvm.org/D144230
1 parent 920b46e commit 63c77bf

File tree

9 files changed

+94
-14
lines changed

9 files changed

+94
-14
lines changed

lldb/source/Commands/CommandObjectDWIMPrint.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,32 @@ bool CommandObjectDWIMPrint::DoExecute(StringRef command,
6262

6363
auto verbosity = GetDebugger().GetDWIMPrintVerbosity();
6464

65+
Target *target_ptr = m_exe_ctx.GetTargetPtr();
66+
// Fallback to the dummy target, which can allow for expression evaluation.
67+
Target &target = target_ptr ? *target_ptr : GetDummyTarget();
68+
69+
const EvaluateExpressionOptions eval_options =
70+
m_expr_options.GetEvaluateExpressionOptions(target, m_varobj_options);
71+
6572
DumpValueObjectOptions dump_options = m_varobj_options.GetAsDumpOptions(
6673
m_expr_options.m_verbosity, m_format_options.GetFormat());
74+
dump_options.SetHideName(eval_options.GetSuppressPersistentResult());
6775

6876
// First, try `expr` as the name of a frame variable.
6977
if (StackFrame *frame = m_exe_ctx.GetFramePtr()) {
7078
auto valobj_sp = frame->FindVariable(ConstString(expr));
7179
if (valobj_sp && valobj_sp->GetError().Success()) {
80+
if (!eval_options.GetSuppressPersistentResult())
81+
valobj_sp = valobj_sp->Persist();
82+
7283
if (verbosity == eDWIMPrintVerbosityFull) {
7384
StringRef flags;
7485
if (args.HasArgs())
7586
flags = args.GetArgString();
7687
result.AppendMessageWithFormatv("note: ran `frame variable {0}{1}`",
7788
flags, expr);
7889
}
90+
7991
valobj_sp->Dump(result.GetOutputStream(), dump_options);
8092
result.SetStatus(eReturnStatusSuccessFinishResult);
8193
return true;
@@ -84,13 +96,7 @@ bool CommandObjectDWIMPrint::DoExecute(StringRef command,
8496

8597
// Second, also lastly, try `expr` as a source expression to evaluate.
8698
{
87-
Target *target_ptr = m_exe_ctx.GetTargetPtr();
88-
// Fallback to the dummy target, which can allow for expression evaluation.
89-
Target &target = target_ptr ? *target_ptr : GetDummyTarget();
90-
9199
auto *exe_scope = m_exe_ctx.GetBestExecutionContextScope();
92-
const EvaluateExpressionOptions eval_options =
93-
m_expr_options.GetEvaluateExpressionOptions(target, m_varobj_options);
94100
ValueObjectSP valobj_sp;
95101
ExpressionResults expr_result =
96102
target.EvaluateExpression(expr, exe_scope, valobj_sp, eval_options);
@@ -102,6 +108,7 @@ bool CommandObjectDWIMPrint::DoExecute(StringRef command,
102108
result.AppendMessageWithFormatv("note: ran `expression {0}{1}`", flags,
103109
expr);
104110
}
111+
105112
valobj_sp->Dump(result.GetOutputStream(), dump_options);
106113
result.SetStatus(eReturnStatusSuccessFinishResult);
107114
return true;

lldb/source/Commands/CommandObjectExpression.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@ Status CommandObjectExpression::CommandOptions::SetOptionValue(
146146
break;
147147
}
148148

149+
case '\x01': {
150+
bool success;
151+
bool persist_result =
152+
OptionArgParser::ToBoolean(option_arg, true, &success);
153+
if (success)
154+
suppress_persistent_result = !persist_result;
155+
else
156+
error.SetErrorStringWithFormat(
157+
"could not convert \"%s\" to a boolean value.",
158+
option_arg.str().c_str());
159+
break;
160+
}
161+
149162
default:
150163
llvm_unreachable("Unimplemented option");
151164
}
@@ -174,6 +187,7 @@ void CommandObjectExpression::CommandOptions::OptionParsingStarting(
174187
auto_apply_fixits = eLazyBoolCalculate;
175188
top_level = false;
176189
allow_jit = true;
190+
suppress_persistent_result = false;
177191
}
178192

179193
llvm::ArrayRef<OptionDefinition>
@@ -186,7 +200,11 @@ CommandObjectExpression::CommandOptions::GetEvaluateExpressionOptions(
186200
const Target &target, const OptionGroupValueObjectDisplay &display_opts) {
187201
EvaluateExpressionOptions options;
188202
options.SetCoerceToId(display_opts.use_objc);
189-
if (m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact)
203+
// Explicitly disabling persistent results takes precedence over the
204+
// m_verbosity/use_objc logic.
205+
if (suppress_persistent_result)
206+
options.SetSuppressPersistentResult(true);
207+
else if (m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact)
190208
options.SetSuppressPersistentResult(display_opts.use_objc);
191209
options.SetUnwindOnError(unwind_on_error);
192210
options.SetIgnoreBreakpoints(ignore_breakpoints);
@@ -405,10 +423,10 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
405423
return false;
406424
}
407425

408-
const EvaluateExpressionOptions options =
426+
const EvaluateExpressionOptions eval_options =
409427
m_command_options.GetEvaluateExpressionOptions(target, m_varobj_options);
410428
ExpressionResults success = target.EvaluateExpression(
411-
expr, frame, result_valobj_sp, options, &m_fixed_expression);
429+
expr, frame, result_valobj_sp, eval_options, &m_fixed_expression);
412430

413431
// We only tell you about the FixIt if we applied it. The compiler errors
414432
// will suggest the FixIt if it parsed.
@@ -437,6 +455,7 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
437455

438456
DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions(
439457
m_command_options.m_verbosity, format));
458+
options.SetHideName(eval_options.GetSuppressPersistentResult());
440459
options.SetVariableFormatDisplayLanguage(
441460
result_valobj_sp->GetPreferredDisplayLanguage());
442461

lldb/source/Commands/CommandObjectExpression.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class CommandObjectExpression : public CommandObjectRaw,
5353
lldb::LanguageType language;
5454
LanguageRuntimeDescriptionDisplayVerbosity m_verbosity;
5555
LazyBool auto_apply_fixits;
56+
bool suppress_persistent_result;
5657
};
5758

5859
CommandObjectExpression(CommandInterpreter &interpreter);

lldb/source/Commands/Options.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,11 @@ let Command = "expression" in {
386386
Arg<"Boolean">,
387387
Desc<"Controls whether the expression can fall back to being JITted if it's "
388388
"not supported by the interpreter (defaults to true).">;
389+
def persistent_result : Option<"persistent-result", "\\x01">, Groups<[1,2]>,
390+
Arg<"Boolean">,
391+
Desc<"Persist expression result in a variable for subsequent use. "
392+
"Expression results will be labeled with $-prefixed variables, e.g. $0, "
393+
"$1, etc. Defaults to true.">;
389394
}
390395

391396
let Command = "frame diag" in {

lldb/source/DataFormatters/ValueObjectPrinter.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,9 @@ bool ValueObjectPrinter::PrintValueAndSummaryIfNeeded(bool &value_printed,
425425
if (m_options.m_hide_pointer_value &&
426426
IsPointerValue(m_valobj->GetCompilerType())) {
427427
} else {
428-
m_stream->Printf(" %s", m_value.c_str());
428+
if (!m_options.m_hide_name)
429+
m_stream->PutChar(' ');
430+
m_stream->PutCString(m_value);
429431
value_printed = true;
430432
}
431433
}

lldb/test/API/commands/dwim-print/TestDWIMPrint.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ def _run_cmd(self, cmd: str) -> str:
1616
self.ci.HandleCommand(cmd, result)
1717
return result.GetOutput().rstrip()
1818

19-
PERSISTENT_VAR = re.compile(r"\$\d+")
19+
VAR_IDENT_RAW = r"(?:\$\d+|\w+) = "
20+
VAR_IDENT = re.compile(VAR_IDENT_RAW)
2021

2122
def _mask_persistent_var(self, string: str) -> str:
2223
"""
2324
Replace persistent result variables (ex '$0', '$1', etc) with a regex
2425
that matches any persistent result (r'\$\d+'). The returned string can
2526
be matched against other `expression` results.
2627
"""
27-
before, after = self.PERSISTENT_VAR.split(string, maxsplit=1)
28-
return re.escape(before) + r"\$\d+" + re.escape(after)
28+
before, after = self.VAR_IDENT.split(string, maxsplit=1)
29+
# Support either a frame variable (\w+) or a persistent result (\$\d+).
30+
return re.escape(before) + self.VAR_IDENT_RAW + re.escape(after)
2931

3032
def _expect_cmd(
3133
self,
@@ -51,7 +53,7 @@ def _expect_cmd(
5153
substrs = [f"note: ran `{resolved_cmd}`"]
5254
patterns = []
5355

54-
if actual_cmd == "expression" and self.PERSISTENT_VAR.search(expected_output):
56+
if self.VAR_IDENT.search(expected_output):
5557
patterns.append(self._mask_persistent_var(expected_output))
5658
else:
5759
substrs.append(expected_output)
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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""
2+
Test controlling `expression` result variables are persistent.
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.lldbtest import *
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
class TestCase(TestBase):
12+
def setUp(self):
13+
TestBase.setUp(self)
14+
self.build()
15+
lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c"))
16+
17+
def test_enable_persistent_result(self):
18+
"""Test explicitly enabling result variables persistence."""
19+
self.expect("expression --persistent-result on -- i", substrs=["(int) $0 = 30"])
20+
# Verify the lifetime of $0 extends beyond the `expression` it was created in.
21+
self.expect("expression $0", substrs=["(int) $0 = 30"])
22+
23+
def test_disable_persistent_result(self):
24+
"""Test explicitly disabling persistent result variables."""
25+
self.expect("expression --persistent-result off -- i", substrs=["(int) 30"])
26+
# Verify a persistent result was not silently created.
27+
self.expect("expression $0", error=True)
28+
29+
def test_expression_persists_result(self):
30+
"""Test `expression`'s default behavior is to persist a result variable."""
31+
self.expect("expression i", substrs=["(int) $0 = 30"])
32+
self.expect("expression $0", substrs=["(int) $0 = 30"])
33+
34+
def test_p_persists_result(self):
35+
"""Test `p` does persist a result variable."""
36+
self.expect("p i", substrs=["(int) $0 = 30"])
37+
self.expect("p $0", substrs=["(int) $0 = 30"])
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int main() {
2+
int i = 30;
3+
return 0; // break here
4+
}

0 commit comments

Comments
 (0)