https://github.com/hawkinsw updated https://github.com/llvm/llvm-project/pull/215706
>From ee25202c7871834e02e1c118e22c6c8b6fc27dae Mon Sep 17 00:00:00 2001 From: Will Hawkins <[email protected]> Date: Tue, 11 Aug 2026 21:46:30 -0400 Subject: [PATCH] [lldb] Support Persistent Variables as DIL Identifiers When a persistent variable is used in a DIL expression, look it up as if it were any other identifier in the global scope. Signed-off-by: Will Hawkins <[email protected]> --- lldb/include/lldb/ValueObject/DILEval.h | 7 ++ .../Commands/CommandObjectDWIMPrint.cpp | 24 +++- lldb/source/ValueObject/DILEval.cpp | 35 ++++++ .../PersistentResultVariableLookup/Makefile | 3 + ...ameVarDILPersistentResultVariableLookup.py | 119 ++++++++++++++++++ .../PersistentResultVariableLookup/main.cpp | 23 ++++ 6 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/Makefile create mode 100644 lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/TestFrameVarDILPersistentResultVariableLookup.py create mode 100644 lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/main.cpp diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index 1510a1f06c7fa..2b8f49c3c440b 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -18,6 +18,13 @@ namespace lldb_private::dil { +/// Given the name of a persistent identifier (i.e., one that starts with a $), +/// find the ValueObject for that name (if it exists). +lldb::ValueObjectSP LookupPersistentIdentifier(llvm::StringRef name_ref, + StackFrame &stack_frame, + lldb::TargetSP target_sp, + lldb::LanguageType language); + /// Given the name of an identifier (variable name, member name, type name, /// etc.), find the ValueObject for that name (if it exists), excluding global /// variables, and create and return an IdentifierInfo object containing all diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp index 1b0b4c7881cfc..2d6dd142aec73 100644 --- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp +++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp @@ -154,14 +154,19 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command, result.SetStatus(eReturnStatusSuccessFinishResult); }; - // First, try `expr` as a _limited_ frame variable expression path: only the - // dot operator (`.`) is permitted for this case. + // Case (1): Try `expr` as a _limited_ frame variable expression path: only + // the dot operator (`.`) is permitted for this case. // // This is limited to support only unambiguous expression paths. Of note, // expression paths are not attempted if the expression contain either the // arrow operator (`->`) or the subscript operator (`[]`). This is because // both operators can be overloaded in C++, and could result in ambiguity in // how the expression is handled. Additionally, `*` and `&` are not supported. + // + // This case handles variable expression paths based on persistent variables, + // but the limitation noted above means that variable expression paths based + // on persistent variables that are ambiguous will be handled by Case (2), + // below. const bool try_variable_path = expr.find_first_of("*&->[]") == StringRef::npos; if (frame && try_variable_path) { @@ -191,17 +196,26 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command, } } - // Second, try `expr` as a persistent variable. + // Case (2): Try `expr` as a persistent variable. if (expr.starts_with("$")) if (auto *state = target.GetPersistentExpressionStateForLanguage( language.AsLanguageType())) - if (auto var_sp = state->GetVariable(expr)) + if (auto var_sp = state->GetVariable(expr)) { if (auto valobj_sp = var_sp->GetValueObject()) { + + if (verbosity == eDWIMPrintVerbosityFull) { + StringRef flags; + if (args.HasArgs()) + flags = args.GetArgString(); + result.AppendNoteWithFormatv("ran `expression {0}{1}`", flags, + expr); + } dump_val_object(*valobj_sp); return; } + } - // Third, and lastly, try `expr` as a source expression to evaluate. + // Case (3): (lastly) Try `expr` as a source expression to evaluate. { auto *exe_scope = m_exe_ctx.GetBestExecutionContextScope(); ValueObjectSP valobj_sp; diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index d448444b43eba..c548dd41fcefd 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -8,10 +8,12 @@ #include "lldb/ValueObject/DILEval.h" #include "lldb/Core/Module.h" +#include "lldb/Expression/ExpressionVariable.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/TypeSystem.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/ValueObject/DILAST.h" #include "lldb/ValueObject/DILParser.h" @@ -44,6 +46,17 @@ static lldb::ValueObjectSP ArrayToPointerConversion(ValueObject &valobj, /* do_deref */ false); } +static llvm::Expected<lldb::LanguageType> +GetSourceLanguageFromCU(StackFrame &ctx) { + SymbolContext symbol_context = + ctx.GetSymbolContext(lldb::eSymbolContextCompUnit); + if (!symbol_context.comp_unit) + return llvm::createStringErrorV("no compile unit for frame: {}", + ctx.GetFunctionName()); + + return symbol_context.comp_unit->GetLanguage(); +} + static llvm::Expected<lldb::TypeSystemSP> GetTypeSystemFromCU(StackFrame &ctx) { SymbolContext symbol_context = ctx.GetSymbolContext(lldb::eSymbolContextCompUnit); @@ -323,6 +336,20 @@ lldb::ValueObjectSP LookupGlobalIdentifier(llvm::StringRef name_ref, return nullptr; } +lldb::ValueObjectSP LookupPersistentIdentifier(llvm::StringRef name_ref, + StackFrame &stack_frame, + lldb::TargetSP target_sp, + lldb::LanguageType language) { + if (name_ref.starts_with("$")) { + if (auto *state = + target_sp->GetPersistentExpressionStateForLanguage(language)) + if (auto var_sp = state->GetVariable(name_ref)) + if (auto valobj_sp = var_sp->GetValueObject()) + return valobj_sp; + } + return nullptr; +} + lldb::ValueObjectSP LookupIdentifier(llvm::StringRef name_ref, StackFrame &stack_frame, lldb::DynamicValueType use_dynamic) { @@ -459,6 +486,14 @@ Interpreter::Visit(const IdentifierNode &node) { if (!identifier) identifier = LookupEnumValue(node.GetName(), m_stack_frame); + if (!identifier && node.GetName()[0] == '$') { + auto language = GetSourceLanguageFromCU(m_stack_frame); + if (!language) + return language.takeError(); + identifier = LookupPersistentIdentifier(node.GetName(), m_stack_frame, + m_target, language.get()); + } + if (!identifier && node.GetName() == "nullptr") { // If we got a "nullptr" identifier, and there is no defined variable with // this name, resolve it as a null pointer. diff --git a/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/Makefile b/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/TestFrameVarDILPersistentResultVariableLookup.py b/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/TestFrameVarDILPersistentResultVariableLookup.py new file mode 100644 index 0000000000000..5d230850bd252 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/TestFrameVarDILPersistentResultVariableLookup.py @@ -0,0 +1,119 @@ +""" +Make sure accessing persistent/result variables works using DIL parser/evaluator. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestFrameVarDILPersistentResultVariableLookup(TestBase): + # If your test case doesn't stress debug info, then + # set this to true. That way it won't be run once for + # each debug info format. + NO_DEBUG_INFO_TESTCASE = True + + def test_frame_var(self): + self.build() + _, process, _, _ = lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + + # Establish persistent variables. + + # Establish a persistent variable with integer type. + self.expect( + "dwim-print --persistent-result true -- foo + 5", startstr="(int) $0 = " + ) + # Establish a persistent variable using dwim-print with derived type (using C specification terminology). + self.expect( + "dwim-print --persistent-result true -- hsmt", + startstr="(HasMembersT) $1 = ", + ) + # Establish a persistent variable using dwim-print as pointer to variable + # with derived type (using C specification terminology). + self.expect( + "dwim-print --persistent-result true -- &hsmt", + startstr="(HasMembersT *) $2 = ", + ) + # Establish result variable using expression. + self.expect( + "expression foo", + startstr="(int) $3 = 1", + ) + # Establish result variables with user-defined names. + self.runCmd( + "expression int *$foop = &foo", + ) + self.runCmd( + "expression HasMembersT *$hsmtp = &hsmt", + ) + + # Test that accessing those persistent/result variables yields the proper values. + + # Check to make sure that `dwim-print`'s note indicates the proper path through the code was taken. + self.runCmd("settings set dwim-print-verbosity full") + # Case (1) checks (see lldb/source/Commands/CommandObjectDWIMPrint.cpp). + self.expect("dwim-print $0", startstr="note: ran `frame variable $0`") + self.expect( + "dwim-print $1.doublem", startstr="note: ran `frame variable $1.doublem`" + ) + # Case (2) check (see lldb/source/Commands/CommandObjectDWIMPrint.cpp). + self.expect( + "dwim-print $2->doublem", startstr="note: ran `expression $2->doublem`" + ) + self.runCmd("settings set dwim-print-verbosity none") + + # Check simple persistent variable accesses. + self.expect_var_path("$0", type="int", value="6") + self.expect_var_path( + "$1", + type="HasMembersT", + children=[ + ValueCheck(name="intm", value="1", type="int"), + ValueCheck(name="doublem", value="2", type="double"), + ValueCheck( + name="nestedm", + type="NestedT", + children=[ValueCheck(name="charm", type="char", value="'c'")], + ), + ], + ) + self.expect_var_path( + "$2", + type="HasMembersT *", + children=[ + ValueCheck(name="intm", value="1", type="int"), + ValueCheck(name="doublem", value="2", type="double"), + ValueCheck( + name="nestedm", + type="NestedT", + children=[ValueCheck(name="charm", type="char", value="'c'")], + ), + ], + ) + self.expect_var_path("$3", type="int", value="1") + + # Check that accessing fields of persistent variables works. + self.expect_var_path("$1.intm", type="int", value="1") + self.expect_var_path("$1.nestedm.charm", type="char", value="'c'") + self.expect_var_path("$1.intm + $0", type="int", value="7") + + # Check that types work correctly when adding an int and a double. + self.expect_var_path("$1.intm + $1.doublem", type="double", value="3") + + self.expect_var_path("*$foop", type="int", value="1") + self.expect_var_path("(*$hsmtp).doublem", type="double", value="2") + + # Step past statements that update variable values to which persistent + # variables refer. + lldbutil.continue_to_source_breakpoint( + self, process, "Set a second breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + # Make sure that the value accessed through the pointer in persistent variables are updated. + self.expect_var_path("*$foop", type="int", value="2") + self.expect_var_path("(*$hsmtp).doublem", type="double", value="3") diff --git a/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/main.cpp b/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/main.cpp new file mode 100644 index 0000000000000..1bdcf11b92480 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/basics/PersistentResultVariableLookup/main.cpp @@ -0,0 +1,23 @@ +typedef struct { + char charm; +} NestedT; + +typedef struct { + int intm; + double doublem; + NestedT nestedm; + +} HasMembersT; + +int main(int argc, char **argv) { + HasMembersT hsmt; + + hsmt.nestedm.charm = 'c'; + hsmt.intm = 1; + hsmt.doublem = 2.0; + + int foo = 1; + foo = 2; // Set a breakpoint here + hsmt.doublem = 3.0; + return 0; // Set a second breakpoint here +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
