[Lldb-commits] [lldb] bb65118 - [lldb] Report a scripted frame's variables with the frame's value type (#221708)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Sep 15 18:53:09 PDT 2026
Author: Med Ismail Bennani
Date: 2026-09-16T01:53:02Z
New Revision: bb651180e3b17b374c5fc0ceb7f3b8c09bc61156
URL: https://github.com/llvm/llvm-project/commit/bb651180e3b17b374c5fc0ceb7f3b8c09bc61156
DIFF: https://github.com/llvm/llvm-project/commit/bb651180e3b17b374c5fc0ceb7f3b8c09bc61156.diff
LOG: [lldb] Report a scripted frame's variables with the frame's value type (#221708)
A scripted frame's variable exists as two objects: a Variable holding
the scope the frame assigned it, and a ValueObject holding what the
frame built. SBValue::GetValueType() reports the ValueObject's, and
GetValueObjectForFrameVariable handed that ValueObject back unchanged,
so the scope never reached a client. A ValueObject built in Python is a
ValueObjectConstResult, which reports how it was produced rather than
which variable it stands for, leaving anything that groups a frame's
variables by scope unable to place it. `frame variable` was unaffected
because it reads the Variables directly.
This patch presents the ValueObject under that scope instead. A class
for this already existed as ValueObjectRecognizerSynthesizedValue, which
frame recognizers use to name an argument: both take a ValueObject that
cannot report the ValueType it is being presented as and supply that
ValueType for it, so generalize that one into
ValueObjectSynthesizedValue and use it for both. ValueObjectVariable is
the other class that pairs a ValueObject with a ValueType, but it cannot
serve here because it recovers its data from a DWARF location
expression, and a frame that builds its own ValueObjects supplies none.
The presented ValueObject is not a parent, so it is no longer held as
one. It is the same ValueObject seen differently, which makes the
presenting object a root: constructing it as one keeps GetParent()
reporting no parent, as it does for any other ValueObject obtained from
a frame, and keeps the walks over m_parent (for a symbol context scope,
a root, a dynamic ValueObject or format information) from finding a
container that does not exist.
Recognized arguments change behaviour slightly as a result: the shared
UpdateValue propagates an error from the presented ValueObject, fills in
the data and sets validity, none of which the recognizer's copy did.
Reaching a variable by name goes through Variable::NameMatches, which
dereferences m_owner_scope for every entry whose name does not match. A
null owner scope is a legal state, CalculateSymbolContext handles it
explicitly and a frame that makes up its own variables has none, so drop
that lookup. The SymbolContext it filled in has had no reader since
22b044877d23, which dropped the language argument to
Mangled::NameMatches along with the only use of it.
Also document that the synthetic flag in GetValueTypeForVariable's
return is what exempts a variable from the scope rules a declared
variable is subject to. A ValueObject with no storage behind it is
filtered out of an in-scope-only listing without it.
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
Added:
lldb/include/lldb/ValueObject/ValueObjectSynthesizedValue.h
lldb/source/ValueObject/ValueObjectSynthesizedValue.cpp
Modified:
lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
lldb/include/lldb/Target/StackFrameRecognizer.h
lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
lldb/source/Symbol/Variable.cpp
lldb/source/ValueObject/CMakeLists.txt
lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
Removed:
################################################################################
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
index b2a37bf497504..b33688d062da3 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
@@ -52,8 +52,20 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
virtual lldb::ValueObjectListSP GetVariables() { return nullptr; }
+ /// Report which kind of variable \a valobj is presented as, for instance
+ /// \a eValueTypeVariableLocal to have it listed among the frame's locals.
+ ///
+ /// A ValueObject the ScriptedFrame built itself should have
+ /// \a eValueTypeSyntheticFlag set in the ValueType it reports. LLDB uses that
+ /// flag to skip the scope rules it applies to a declared variable, which a
+ /// made-up ValueObject cannot satisfy: it has no storage to locate, so an
+ /// in-scope-only listing drops it. A variable the frame is only forwarding
+ /// should leave the flag clear, so those rules still apply to it.
+ ///
+ /// If this returns std::nullopt, LLDB keeps the ValueType the ValueObject
+ /// already reports and adds the synthetic flag to it.
virtual std::optional<lldb::ValueType>
- GetValueTypeForVariable(lldb::ValueObjectSP value) {
+ GetValueTypeForVariable(lldb::ValueObjectSP valobj) {
return std::nullopt;
}
diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h
index c9ef6e6fb141b..f6475c2bcd3a0 100644
--- a/lldb/include/lldb/Target/StackFrameRecognizer.h
+++ b/lldb/include/lldb/Target/StackFrameRecognizer.h
@@ -14,6 +14,7 @@
#include "lldb/Utility/StructuredData.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/ValueObject/ValueObjectList.h"
+#include "lldb/ValueObject/ValueObjectSynthesizedValue.h"
#include "lldb/lldb-private-forward.h"
#include "lldb/lldb-public.h"
@@ -165,42 +166,9 @@ class StackFrameRecognizerManager {
uint16_t m_generation = 0;
};
-/// \class ValueObjectRecognizerSynthesizedValue
-///
-/// ValueObject subclass that presents the passed ValueObject as a recognized
-/// value with the specified ValueType. Frame recognizers should return
-/// instances of this class as the returned objects in GetRecognizedArguments().
-class ValueObjectRecognizerSynthesizedValue : public ValueObject {
- public:
- static lldb::ValueObjectSP Create(ValueObject &parent, lldb::ValueType type) {
- return (new ValueObjectRecognizerSynthesizedValue(parent, type))->GetSP();
- }
- ValueObjectRecognizerSynthesizedValue(ValueObject &parent,
- lldb::ValueType type)
- : ValueObject(parent), m_type(type) {
- SetName(parent.GetName());
- }
-
- llvm::Expected<uint64_t> GetByteSize() override {
- return m_parent->GetByteSize();
- }
- lldb::ValueType GetValueType() const override { return m_type; }
- bool UpdateValue() override {
- if (!m_parent->UpdateValueIfNeeded()) return false;
- m_value = m_parent->GetValue();
- return true;
- }
- llvm::Expected<uint32_t>
- CalculateNumChildren(uint32_t max = UINT32_MAX) override {
- return m_parent->GetNumChildren(max);
- }
- CompilerType GetCompilerTypeImpl() override {
- return m_parent->GetCompilerType();
- }
-
- private:
- lldb::ValueType m_type;
-};
+/// Frame recognizers should return ValueObjectSynthesizedValue instances as the
+/// returned objects in GetRecognizedArguments().
+using ValueObjectRecognizerSynthesizedValue = ValueObjectSynthesizedValue;
} // namespace lldb_private
diff --git a/lldb/include/lldb/ValueObject/ValueObjectSynthesizedValue.h b/lldb/include/lldb/ValueObject/ValueObjectSynthesizedValue.h
new file mode 100644
index 0000000000000..1143d5d86e925
--- /dev/null
+++ b/lldb/include/lldb/ValueObject/ValueObjectSynthesizedValue.h
@@ -0,0 +1,65 @@
+//===-- ValueObjectSynthesizedValue.h ---------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_VALUEOBJECT_VALUEOBJECTSYNTHESIZEDVALUE_H
+#define LLDB_VALUEOBJECT_VALUEOBJECTSYNTHESIZEDVALUE_H
+
+#include "lldb/ValueObject/ValueObject.h"
+#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
+
+namespace lldb_private {
+
+/// \class ValueObjectSynthesizedValue
+///
+/// Presents another ValueObject under the given ValueType.
+///
+/// A ValueObject reports the ValueType that follows from how it was produced,
+/// which is not always what it is being presented as: a frame that synthesizes
+/// variables, or a frame recognizer naming an argument, knows a ValueType the
+/// ValueObject cannot report for itself. This ValueObject subclass reports that
+/// ValueType and forwards everything else to the ValueObject it presents.
+///
+/// The presented ValueObject is not a parent. It is the same ValueObject seen
+///
diff erently, so this is a root and reports no parent.
+class ValueObjectSynthesizedValue : public ValueObject {
+public:
+ static lldb::ValueObjectSP Create(ValueObject &valobj, lldb::ValueType type);
+
+ ~ValueObjectSynthesizedValue() override;
+
+ lldb::ValueType GetValueType() const override { return m_type; }
+
+ llvm::Expected<uint64_t> GetByteSize() override;
+
+ llvm::Expected<uint32_t> CalculateNumChildren(uint32_t max) override;
+
+ bool IsInScope() override;
+
+protected:
+ ValueObjectSynthesizedValue(ExecutionContextScope *exe_scope,
+ ValueObjectManager &manager,
+ const lldb::ValueObjectSP &valobj_sp,
+ lldb::ValueType type);
+
+ bool UpdateValue() override;
+
+ CompilerType GetCompilerTypeImpl() override;
+
+ lldb::ValueObjectSP m_valobj_sp;
+ lldb::ValueType m_type;
+
+private:
+ ValueObjectSynthesizedValue(const ValueObjectSynthesizedValue &) = delete;
+ const ValueObjectSynthesizedValue &
+ operator=(const ValueObjectSynthesizedValue &) = delete;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_VALUEOBJECT_VALUEOBJECTSYNTHESIZEDVALUE_H
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index cac9f0e44bb73..09b20fafd5e85 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -33,6 +33,7 @@
#include "lldb/Utility/ValueType.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/ValueObject/ValueObjectList.h"
+#include "lldb/ValueObject/ValueObjectSynthesizedValue.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-forward.h"
#include "llvm/Support/ErrorHandling.h"
@@ -305,8 +306,13 @@ lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable(
if (!values)
return {};
- return values->FindValueObjectByValueName(
+ lldb::ValueObjectSP valobj_sp = values->FindValueObjectByValueName(
variable_sp->GetName().AsCString(nullptr));
+ if (!valobj_sp)
+ return {};
+
+ return ValueObjectSynthesizedValue::Create(*valobj_sp,
+ variable_sp->GetScope());
}
lldb::ValueObjectSP ScriptedFrame::FindVariable(ConstString name) {
@@ -315,7 +321,20 @@ lldb::ValueObjectSP ScriptedFrame::FindVariable(ConstString name) {
if (!values)
return {};
- return values->FindValueObjectByValueName(name.AsCString(nullptr));
+ lldb::ValueObjectSP valobj_sp =
+ values->FindValueObjectByValueName(name.AsCString(nullptr));
+ if (!valobj_sp)
+ return {};
+
+ // Present the ValueObject under the same ValueType the frame's variable list
+ // would, so reaching a variable by name and by enumeration agree.
+ if (m_variable_list_sp) {
+ if (VariableSP variable_sp = m_variable_list_sp->FindVariable(name))
+ return ValueObjectSynthesizedValue::Create(*valobj_sp,
+ variable_sp->GetScope());
+ }
+
+ return valobj_sp;
}
lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
diff --git a/lldb/source/Symbol/Variable.cpp b/lldb/source/Symbol/Variable.cpp
index 3fc5c1cc38408..8ada6f6f4d2c7 100644
--- a/lldb/source/Symbol/Variable.cpp
+++ b/lldb/source/Symbol/Variable.cpp
@@ -92,9 +92,6 @@ ConstString Variable::GetUnqualifiedName() const { return m_name; }
bool Variable::NameMatches(ConstString name) const {
if (m_name == name)
return true;
- SymbolContext variable_sc;
- m_owner_scope->CalculateSymbolContext(&variable_sc);
-
return m_mangled.NameMatches(name);
}
bool Variable::NameMatches(const RegularExpression ®ex) const {
diff --git a/lldb/source/ValueObject/CMakeLists.txt b/lldb/source/ValueObject/CMakeLists.txt
index f0fe7f374a506..a8a745cdb8376 100644
--- a/lldb/source/ValueObject/CMakeLists.txt
+++ b/lldb/source/ValueObject/CMakeLists.txt
@@ -14,6 +14,7 @@ add_lldb_library(lldbValueObject NO_PLUGIN_DEPENDENCIES
ValueObjectList.cpp
ValueObjectMemory.cpp
ValueObjectRegister.cpp
+ ValueObjectSynthesizedValue.cpp
ValueObjectSynthetic.cpp
ValueObjectUpdater.cpp
ValueObjectVariable.cpp
diff --git a/lldb/source/ValueObject/ValueObjectSynthesizedValue.cpp b/lldb/source/ValueObject/ValueObjectSynthesizedValue.cpp
new file mode 100644
index 0000000000000..8c8e1418a7034
--- /dev/null
+++ b/lldb/source/ValueObject/ValueObjectSynthesizedValue.cpp
@@ -0,0 +1,71 @@
+//===-- ValueObjectSynthesizedValue.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/ValueObject/ValueObjectSynthesizedValue.h"
+
+#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Utility/Status.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+lldb::ValueObjectSP ValueObjectSynthesizedValue::Create(ValueObject &valobj,
+ lldb::ValueType type) {
+ ExecutionContext exe_ctx(valobj.GetExecutionContextRef());
+ auto manager_sp = ValueObjectManager::Create();
+ return (new ValueObjectSynthesizedValue(
+ exe_ctx.GetBestExecutionContextScope(), *manager_sp,
+ valobj.GetSP(), type))
+ ->GetSP();
+}
+
+ValueObjectSynthesizedValue::ValueObjectSynthesizedValue(
+ ExecutionContextScope *exe_scope, ValueObjectManager &manager,
+ const lldb::ValueObjectSP &valobj_sp, lldb::ValueType type)
+ : ValueObject(exe_scope, manager), m_valobj_sp(valobj_sp), m_type(type) {
+ SetName(valobj_sp->GetName());
+}
+
+ValueObjectSynthesizedValue::~ValueObjectSynthesizedValue() = default;
+
+llvm::Expected<uint64_t> ValueObjectSynthesizedValue::GetByteSize() {
+ return m_valobj_sp->GetByteSize();
+}
+
+llvm::Expected<uint32_t>
+ValueObjectSynthesizedValue::CalculateNumChildren(uint32_t max) {
+ return m_valobj_sp->GetNumChildren(max);
+}
+
+bool ValueObjectSynthesizedValue::IsInScope() {
+ return m_valobj_sp->IsInScope();
+}
+
+CompilerType ValueObjectSynthesizedValue::GetCompilerTypeImpl() {
+ return m_valobj_sp->GetCompilerType();
+}
+
+bool ValueObjectSynthesizedValue::UpdateValue() {
+ SetValueIsValid(false);
+ m_error.Clear();
+
+ if (!m_valobj_sp->UpdateValueIfNeeded(false)) {
+ if (m_error.Success() && m_valobj_sp->GetError().Fail())
+ m_error = m_valobj_sp->GetError().Clone();
+ return false;
+ }
+
+ m_update_point.SetUpdated();
+ m_value = m_valobj_sp->GetValue();
+ SetAddressTypeOfChildren(m_valobj_sp->GetAddressTypeOfChildren());
+ ExecutionContext exe_ctx(GetExecutionContextRef());
+ m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get());
+ SetValueDidChange(m_valobj_sp->GetValueDidChange());
+ SetValueIsValid(m_error.Success());
+ return m_error.Success();
+}
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index eb327d2a34c90..9b2c290b29558 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -866,6 +866,23 @@ def test_get_values(self):
self.assertEqual(variables.GetSize(), 1)
self.assertEqual(variables.GetValueAtIndex(0).name, "_handler_one")
+ # A variable reports the ValueType the frame classified it as, not the
+ # one that follows from how its ValueObject was built. `_handler_one`
+ # comes from an expression, so the two
diff er.
+ self.assertEqual(
+ variables.GetValueAtIndex(0).GetValueType(),
+ lldb.eValueTypeVariableLocal | lldb.eValueTypeSyntheticFlag,
+ )
+ # Classifying a variable must not cost its data or its type.
+ self.assertEqual(variables.GetValueAtIndex(0).GetValueAsUnsigned(), 1)
+ self.assertEqual(variables.GetValueAtIndex(0).GetTypeName(), "uint32_t")
+ # Reaching the same variable by name has to agree with the enumeration.
+ self.assertEqual(
+ frame0.FindVariable("_handler_one").GetValueType(),
+ lldb.eValueTypeVariableLocal | lldb.eValueTypeSyntheticFlag,
+ )
+ self.assertEqual(frame0.FindVariable("_handler_one").GetValueAsUnsigned(), 1)
+
# Check the `frame variable` command(s) handle synthetic variables the
# way we expect by printing them.
self.expect("frame var", substrs=["variable_in_main", "_handler_one"])
More information about the lldb-commits
mailing list