[Lldb-commits] [lldb] [lldb] Support Persistent Variables as DIL Identifiers (PR #215706)
Will Hawkins via lldb-commits
lldb-commits at lists.llvm.org
Fri Aug 21 21:40:46 PDT 2026
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 <hawkinsw at obs.cr>
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 <hawkinsw at obs.cr>
---
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
+}
More information about the lldb-commits
mailing list