diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp --- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp +++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp @@ -64,11 +64,16 @@ DumpValueObjectOptions dump_options = m_varobj_options.GetAsDumpOptions( m_expr_options.m_verbosity, m_format_options.GetFormat()); + if (m_expr_options.suppress_persistent_result) + dump_options.SetHideName(true); // First, try `expr` as the name of a frame variable. if (StackFrame *frame = m_exe_ctx.GetFramePtr()) { auto valobj_sp = frame->FindVariable(ConstString(expr)); if (valobj_sp && valobj_sp->GetError().Success()) { + if (!m_expr_options.suppress_persistent_result) + valobj_sp = valobj_sp->Persist(); + if (verbosity == eDWIMPrintVerbosityFull) { StringRef flags; if (args.HasArgs()) @@ -76,6 +81,7 @@ result.AppendMessageWithFormatv("note: ran `frame variable {0}{1}`", flags, expr); } + valobj_sp->Dump(result.GetOutputStream(), dump_options); result.SetStatus(eReturnStatusSuccessFinishResult); return true; @@ -102,6 +108,7 @@ result.AppendMessageWithFormatv("note: ran `expression {0}{1}`", flags, expr); } + valobj_sp->Dump(result.GetOutputStream(), dump_options); result.SetStatus(eReturnStatusSuccessFinishResult); return true; diff --git a/lldb/source/Commands/CommandObjectExpression.h b/lldb/source/Commands/CommandObjectExpression.h --- a/lldb/source/Commands/CommandObjectExpression.h +++ b/lldb/source/Commands/CommandObjectExpression.h @@ -53,6 +53,7 @@ lldb::LanguageType language; LanguageRuntimeDescriptionDisplayVerbosity m_verbosity; LazyBool auto_apply_fixits; + bool suppress_persistent_result; }; CommandObjectExpression(CommandInterpreter &interpreter); diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -146,6 +146,21 @@ break; } + case 'C': { + // 'C' for "caching", since both 'P' and 'p' for persist are taken. Both 'R' + // flags are taken too. + bool success; + bool persist_result = + OptionArgParser::ToBoolean(option_arg, true, &success); + if (success) + suppress_persistent_result = !persist_result; + else + error.SetErrorStringWithFormat( + "could not convert \"%s\" to a boolean value.", + option_arg.str().c_str()); + break; + } + default: llvm_unreachable("Unimplemented option"); } @@ -174,6 +189,7 @@ auto_apply_fixits = eLazyBoolCalculate; top_level = false; allow_jit = true; + suppress_persistent_result = false; } llvm::ArrayRef @@ -186,7 +202,9 @@ const Target &target, const OptionGroupValueObjectDisplay &display_opts) { EvaluateExpressionOptions options; options.SetCoerceToId(display_opts.use_objc); - if (m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact) + if (suppress_persistent_result) + options.SetSuppressPersistentResult(true); + else if (m_verbosity == eLanguageRuntimeDescriptionDisplayVerbosityCompact) options.SetSuppressPersistentResult(display_opts.use_objc); options.SetUnwindOnError(unwind_on_error); options.SetIgnoreBreakpoints(ignore_breakpoints); @@ -437,6 +455,8 @@ DumpValueObjectOptions options(m_varobj_options.GetAsDumpOptions( m_command_options.m_verbosity, format)); + if (m_command_options.suppress_persistent_result) + options.SetHideName(true); options.SetVariableFormatDisplayLanguage( result_valobj_sp->GetPreferredDisplayLanguage()); diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -386,6 +386,11 @@ Arg<"Boolean">, Desc<"Controls whether the expression can fall back to being JITted if it's " "not supported by the interpreter (defaults to true).">; + def persistent_result : Option<"persistent-result", "C">, Groups<[1,2]>, + Arg<"Boolean">, + Desc<"Persist expression result in a variable for subsequent use. " + "Expression results will be labeled with $-prefixed variables, e.g. $0, " + "$1, etc. Defaults to true.">; } let Command = "frame diag" in { diff --git a/lldb/source/DataFormatters/ValueObjectPrinter.cpp b/lldb/source/DataFormatters/ValueObjectPrinter.cpp --- a/lldb/source/DataFormatters/ValueObjectPrinter.cpp +++ b/lldb/source/DataFormatters/ValueObjectPrinter.cpp @@ -425,7 +425,9 @@ if (m_options.m_hide_pointer_value && IsPointerValue(m_valobj->GetCompilerType())) { } else { - m_stream->Printf(" %s", m_value.c_str()); + if (!m_options.m_hide_name) + m_stream->PutChar(' '); + m_stream->PutCString(m_value); value_printed = true; } } diff --git a/lldb/test/API/commands/dwim-print/TestDWIMPrint.py b/lldb/test/API/commands/dwim-print/TestDWIMPrint.py --- a/lldb/test/API/commands/dwim-print/TestDWIMPrint.py +++ b/lldb/test/API/commands/dwim-print/TestDWIMPrint.py @@ -16,7 +16,8 @@ self.ci.HandleCommand(cmd, result) return result.GetOutput().rstrip() - PERSISTENT_VAR = re.compile(r"\$\d+") + VAR_IDENT_RAW = r"(?:\$\d+|\w+) = " + VAR_IDENT = re.compile(VAR_IDENT_RAW) def _mask_persistent_var(self, string: str) -> str: """ @@ -24,8 +25,9 @@ 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) + before, after = self.VAR_IDENT.split(string, maxsplit=1) + # Support either a frame variable (\w+) or a persistent result (\$\d+). + return re.escape(before) + self.VAR_IDENT_RAW + re.escape(after) def _expect_cmd( self, @@ -51,7 +53,7 @@ substrs = [f"note: ran `{resolved_cmd}`"] patterns = [] - if actual_cmd == "expression" and self.PERSISTENT_VAR.search(expected_output): + if self.VAR_IDENT.search(expected_output): patterns.append(self._mask_persistent_var(expected_output)) else: substrs.append(expected_output) diff --git a/lldb/test/API/commands/expression/persistent_result/Makefile b/lldb/test/API/commands/expression/persistent_result/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/persistent_result/Makefile @@ -0,0 +1,3 @@ +OBJC_SOURCES := main.m + +include Makefile.rules diff --git a/lldb/test/API/commands/expression/persistent_result/TestPersistentResult.py b/lldb/test/API/commands/expression/persistent_result/TestPersistentResult.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/persistent_result/TestPersistentResult.py @@ -0,0 +1,37 @@ +""" +Test controlling `expression` result variables are persistent. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestCase(TestBase): + def setUp(self): + TestBase.setUp(self) + self.build() + lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c")) + + def test_enable_persistent_result(self): + """Test explicitly enabling result variables persistence.""" + self.expect("expression --persistent-result on -- i", substrs=["(int) $0 = 30"]) + # Verify the lifetime of $0 extends beyond the `expression` it was created in. + self.expect("expression $0", substrs=["(int) $0 = 30"]) + + def test_disable_persistent_result(self): + """Test explicitly disabling persistent result variables.""" + self.expect("expression --persistent-result off -- i", substrs=["(int) 30"]) + # Verify a persistent result was not silently created. + self.expect("expression $0", error=True) + + def test_expression_persists_result(self): + """Test `expression`'s default behavior is to persist a result variable.""" + self.expect("expression i", substrs=["(int) $0 = 30"]) + self.expect("expression $0", substrs=["(int) $0 = 30"]) + + def test_p_persists_result(self): + """Test `p` does persist a result variable.""" + self.expect("p i", substrs=["(int) $0 = 30"]) + self.expect("p $0", substrs=["(int) $0 = 30"]) diff --git a/lldb/test/API/commands/expression/persistent_result/main.c b/lldb/test/API/commands/expression/persistent_result/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/persistent_result/main.c @@ -0,0 +1,4 @@ +int main() { + int i = 30; + return 0; // break here +}