Skip to content

Commit 00c20f0

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 (cherry picked from commit 63c77bf)
1 parent b8db2a9 commit 00c20f0

File tree

9 files changed

+96
-16
lines changed

9 files changed

+96
-16
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: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,21 @@ Status CommandObjectExpression::CommandOptions::SetOptionValue(
145145
break;
146146
}
147147

148-
// BEGIN SWIFT
149148
case '\x01': {
149+
bool success;
150+
bool persist_result =
151+
OptionArgParser::ToBoolean(option_arg, true, &success);
152+
if (success)
153+
suppress_persistent_result = !persist_result;
154+
else
155+
error.SetErrorStringWithFormat(
156+
"could not convert \"%s\" to a boolean value.",
157+
option_arg.str().c_str());
158+
break;
159+
}
160+
161+
// BEGIN SWIFT
162+
case '\x31': {
150163
int32_t result;
151164
result = OptionArgParser::ToOptionEnum(option_arg, BindGenTypeParamValue(),
152165
0, error);
@@ -184,6 +197,7 @@ void CommandObjectExpression::CommandOptions::OptionParsingStarting(
184197
auto_apply_fixits = eLazyBoolCalculate;
185198
top_level = false;
186199
allow_jit = true;
200+
suppress_persistent_result = false;
187201
// BEGIN SWIFT
188202
bind_generic_types = eBindAuto;
189203
// END SWIFT
@@ -199,7 +213,11 @@ CommandObjectExpression::CommandOptions::GetEvaluateExpressionOptions(
199213
const Target &target, const OptionGroupValueObjectDisplay &display_opts) {
200214
EvaluateExpressionOptions options;
201215
options.SetCoerceToId(display_opts.use_objc);
202-
if (m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact)
216+
// Explicitly disabling persistent results takes precedence over the
217+
// m_verbosity/use_objc logic.
218+
if (suppress_persistent_result)
219+
options.SetSuppressPersistentResult(true);
220+
else if (m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact)
203221
options.SetSuppressPersistentResult(display_opts.use_objc);
204222
options.SetUnwindOnError(unwind_on_error);
205223
options.SetIgnoreBreakpoints(ignore_breakpoints);
@@ -432,10 +450,10 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
432450
return false;
433451
}
434452

435-
const EvaluateExpressionOptions options =
453+
const EvaluateExpressionOptions eval_options =
436454
m_command_options.GetEvaluateExpressionOptions(target, m_varobj_options);
437455
ExpressionResults success = target.EvaluateExpression(
438-
expr, frame, result_valobj_sp, options, &m_fixed_expression);
456+
expr, frame, result_valobj_sp, eval_options, &m_fixed_expression);
439457

440458
// We only tell you about the FixIt if we applied it. The compiler errors
441459
// will suggest the FixIt if it parsed.
@@ -464,6 +482,7 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
464482

465483
DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions(
466484
m_command_options.m_verbosity, format));
485+
options.SetHideName(eval_options.GetSuppressPersistentResult());
467486
options.SetVariableFormatDisplayLanguage(
468487
result_valobj_sp->GetPreferredDisplayLanguage());
469488

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
// BEGIN SWIFT
5758
lldb::BindGenericTypes bind_generic_types;
5859
// END SWIFT

lldb/source/Commands/Options.td

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,13 @@ let Command = "expression" in {
387387
Arg<"Boolean">,
388388
Desc<"Controls whether the expression can fall back to being JITted if it's "
389389
"not supported by the interpreter (defaults to true).">;
390+
def persistent_result : Option<"persistent-result", "\\x01">, Groups<[1,2]>,
391+
Arg<"Boolean">,
392+
Desc<"Persist expression result in a variable for subsequent use. "
393+
"Expression results will be labeled with $-prefixed variables, e.g. $0, "
394+
"$1, etc. Defaults to true.">;
390395
// BEGIN SWIFT
391-
def bind_generic_types : Option<"bind-generic-types", "\\x01">,
396+
def bind_generic_types : Option<"bind-generic-types", "\\x31">,
392397
EnumArg<"BindGenTypeParamValue">, Desc<"Controls whether any "
393398
"generic types in the current context should be bound to their dynamic "
394399
"type before evaluating. Defaults to auto.">;

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)