https://github.com/bzcheeseman updated 
https://github.com/llvm/llvm-project/pull/178571

>From 5a00664e7ba135b707e6e39e5e025ec7561ded5b Mon Sep 17 00:00:00 2001
From: bzcheeseman <[email protected]>
Date: Wed, 28 Jan 2026 16:35:22 -0800
Subject: [PATCH] [lldb] Add support for ScriptedFrame to provide
 values/variables.

This patch adds plumbing to support the implementations of 
StackFrame::Get{*}Variable{*} on ScriptedFrame. The major pieces required are:
- A modification to ScriptedFrameInterface, so that we can actually call the 
python methods.
- A corresponding update to the python implementation to call the python 
methods.
- An implementation in ScriptedFrame that can get the variable list on 
construction inside ScriptedFrame::Create, and pass that list into the 
ScriptedFrame so it can get those values on request.

There is a major caveat, which is that if the values from the python side don't 
have variables attached, right now, they won't be passed into the scripted 
frame to be stored in the variable list. Future discussions around adding 
support for 'extended variables' when printing frame variables may create a 
reason to change the VariableListSP into a ValueObjectListSP, and generate the 
VariableListSP on the fly, but that should be addressed at a later time.

This patch also adds tests to the frame provider test suite to prove these 
changes all plumb together correctly.

stack-info: PR: https://github.com/llvm/llvm-project/pull/178571, branch: 
users/bzcheeseman/stack/3
---
 .../Interfaces/ScriptedFrameInterface.h       | 11 +++
 .../Process/scripted/ScriptedFrame.cpp        | 77 +++++++++++++++--
 .../Plugins/Process/scripted/ScriptedFrame.h  | 21 ++++-
 .../ScriptedFramePythonInterface.cpp          | 27 ++++++
 .../Interfaces/ScriptedFramePythonInterface.h |  6 ++
 .../TestScriptedFrameProvider.py              | 53 ++++++++++++
 .../scripted_frame_provider/main.cpp          |  4 +
 .../test_frame_providers.py                   | 82 +++++++++++++++++++
 8 files changed, 275 insertions(+), 6 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h 
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
index 8ef4b37d6ba12..90170d3bead5f 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
@@ -10,6 +10,7 @@
 #define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEINTERFACE_H
 
 #include "ScriptedInterface.h"
+#include "lldb/API/SBValueList.h"
 #include "lldb/Core/StructuredDataImpl.h"
 #include "lldb/Symbol/SymbolContext.h"
 #include "lldb/lldb-private.h"
@@ -49,6 +50,16 @@ class ScriptedFrameInterface : virtual public 
ScriptedInterface {
   virtual std::optional<std::string> GetRegisterContext() {
     return std::nullopt;
   }
+
+  virtual std::optional<lldb::ValueObjectListSP> GetVariables() {
+    return std::nullopt;
+  }
+
+  virtual lldb::ValueObjectSP
+  GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
+                                      Status &error) {
+    return nullptr;
+  }
 };
 } // namespace lldb_private
 
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp 
b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 70ce101c6c834..caf37dc71e03e 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -9,6 +9,7 @@
 #include "ScriptedFrame.h"
 #include "Plugins/Process/Utility/RegisterContextMemory.h"
 
+#include "lldb/API/SBDeclaration.h"
 #include "lldb/Core/Address.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Module.h"
@@ -20,6 +21,7 @@
 #include "lldb/Symbol/CompileUnit.h"
 #include "lldb/Symbol/SymbolContext.h"
 #include "lldb/Symbol/SymbolFile.h"
+#include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/ExecutionContext.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/RegisterContext.h"
@@ -28,6 +30,8 @@
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/StructuredData.h"
+#include "lldb/ValueObject/ValueObject.h"
+#include "lldb/ValueObject/ValueObjectList.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -106,6 +110,30 @@ ScriptedFrame::Create(ThreadSP thread_sp,
   if (maybe_sym_ctx)
     sc = *maybe_sym_ctx;
 
+  // If we have any variables from the scripted interface, pull them out and 
add
+  // them to the ScriptedFrame object.
+  std::optional<ValueObjectListSP> maybe_variables =
+      scripted_frame_interface->GetVariables();
+  lldb::VariableListSP variable_list_sp = nullptr;
+  if (maybe_variables && *maybe_variables) {
+    variable_list_sp = std::make_shared<VariableList>();
+    ValueObjectListSP value_list_sp = *maybe_variables;
+
+    for (uint32_t i = 0, e = value_list_sp->GetSize(); i < e; ++i) {
+      ValueObjectSP v = value_list_sp->GetValueObjectAtIndex(i);
+      if (!v)
+        continue;
+
+      VariableSP var = v->GetVariable();
+      // TODO: We could in theory ask the scripted frame to *produce* a
+      //       variable for this value object.
+      if (!var)
+        continue;
+
+      variable_list_sp->AddVariable(var);
+    }
+  }
+
   lldb::RegisterContextSP reg_ctx_sp;
   auto regs_or_err =
       CreateRegisterContext(*scripted_frame_interface, *thread_sp, frame_id);
@@ -114,9 +142,10 @@ ScriptedFrame::Create(ThreadSP thread_sp,
   else
     reg_ctx_sp = *regs_or_err;
 
-  return std::make_shared<ScriptedFrame>(thread_sp, scripted_frame_interface,
-                                         frame_id, pc, sc, reg_ctx_sp,
-                                         owned_script_object_sp);
+  return std::make_shared<ScriptedFrame>(
+      thread_sp, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
+      owned_script_object_sp, variable_list_sp,
+      maybe_variables.value_or(nullptr));
 }
 
 ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
@@ -124,13 +153,16 @@ ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
                              lldb::user_id_t id, lldb::addr_t pc,
                              SymbolContext &sym_ctx,
                              lldb::RegisterContextSP reg_ctx_sp,
-                             StructuredData::GenericSP script_object_sp)
+                             StructuredData::GenericSP script_object_sp,
+                             lldb::VariableListSP variables_sp,
+                             lldb::ValueObjectListSP values_sp)
     : StackFrame(thread_sp, /*frame_idx=*/id,
                  /*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp,
                  /*cfa=*/0, /*pc=*/pc,
                  /*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx),
       m_scripted_frame_interface_sp(interface_sp),
-      m_script_object_sp(script_object_sp) {
+      m_script_object_sp(script_object_sp), m_variable_list_sp(variables_sp),
+      m_value_objects_sp(values_sp) {
   // FIXME: This should be part of the base class constructor.
   m_stack_frame_kind = StackFrame::Kind::Synthetic;
 }
@@ -265,3 +297,38 @@ lldb::RegisterContextSP 
ScriptedFrame::GetRegisterContext() {
 
   return m_reg_context_sp;
 }
+
+VariableList *ScriptedFrame::GetVariableList(bool get_file_globals,
+                                             Status *error_ptr) {
+  return m_variable_list_sp.get();
+}
+
+lldb::VariableListSP
+ScriptedFrame::GetInScopeVariableList(bool get_file_globals,
+                                      bool must_have_valid_location) {
+  return m_variable_list_sp;
+}
+
+lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable(
+    const lldb::VariableSP &variable_sp, lldb::DynamicValueType use_dynamic) {
+  for (size_t i = 0, e = m_variable_list_sp->GetSize(); i < e; ++i) {
+    if (m_variable_list_sp->GetVariableAtIndex(i) == variable_sp) {
+      return m_value_objects_sp->GetValueObjectAtIndex(i);
+    }
+  }
+  return nullptr;
+}
+
+lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
+    llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
+    uint32_t options, lldb::VariableSP &var_sp, Status &error) {
+  // Unless the frame implementation knows how to create variables (which it
+  // doesn't), we can't construct anything for the variable. This may seem
+  // somewhat out of place, but it's basically because of how this API is used 
-
+  // the print command uses this API to fill in var_sp; and this implementation
+  // can't do that!
+  (void)var_sp;
+  // Otherwise, delegate to the scripted frame interface pointer.
+  return m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
+      var_expr, options, error);
+}
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h 
b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
index 0545548e912e6..5aef8f58b1aae 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
@@ -26,7 +26,9 @@ class ScriptedFrame : public lldb_private::StackFrame {
                 lldb::ScriptedFrameInterfaceSP interface_sp,
                 lldb::user_id_t frame_idx, lldb::addr_t pc,
                 SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp,
-                StructuredData::GenericSP script_object_sp = nullptr);
+                StructuredData::GenericSP script_object_sp = nullptr,
+                lldb::VariableListSP variables_sp = nullptr,
+                lldb::ValueObjectListSP values_sp = nullptr);
 
   ~ScriptedFrame() override;
 
@@ -63,6 +65,21 @@ class ScriptedFrame : public lldb_private::StackFrame {
 
   lldb::RegisterContextSP GetRegisterContext() override;
 
+  VariableList *GetVariableList(bool get_file_globals,
+                                lldb_private::Status *error_ptr) override;
+
+  lldb::VariableListSP
+  GetInScopeVariableList(bool get_file_globals,
+                         bool must_have_valid_location = false) override;
+
+  lldb::ValueObjectSP
+  GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp,
+                                 lldb::DynamicValueType use_dynamic) override;
+
+  lldb::ValueObjectSP GetValueForVariableExpressionPath(
+      llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
+      uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
+
   bool isA(const void *ClassID) const override {
     return ClassID == &ID || StackFrame::isA(ClassID);
   }
@@ -82,6 +99,8 @@ class ScriptedFrame : public lldb_private::StackFrame {
 
   lldb::ScriptedFrameInterfaceSP m_scripted_frame_interface_sp;
   lldb_private::StructuredData::GenericSP m_script_object_sp;
+  lldb::VariableListSP m_variable_list_sp;
+  lldb::ValueObjectListSP m_value_objects_sp;
 
   static char ID;
 };
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
index 20ca7a2c01356..3782d9729bbf9 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
@@ -154,4 +154,31 @@ std::optional<std::string> 
ScriptedFramePythonInterface::GetRegisterContext() {
   return obj->GetAsString()->GetValue().str();
 }
 
+std::optional<lldb::ValueObjectListSP>
+ScriptedFramePythonInterface::GetVariables() {
+  Status error;
+  auto vals = Dispatch<lldb::ValueObjectListSP>("get_variables", error);
+
+  if (error.Fail()) {
+    return ErrorWithMessage<lldb::ValueObjectListSP>(LLVM_PRETTY_FUNCTION,
+                                                     error.AsCString(), error);
+  }
+
+  return vals;
+}
+
+lldb::ValueObjectSP 
ScriptedFramePythonInterface::GetValueObjectForVariableExpression(
+    llvm::StringRef expr, uint32_t options, Status &status) {
+  Status dispatch_error;
+  auto val = Dispatch<lldb::ValueObjectSP>(
+      "get_value_for_variable_expression", dispatch_error, expr.data(), 
options, status);
+
+  if (dispatch_error.Fail()) {
+    return ErrorWithMessage<lldb::ValueObjectSP>(
+        LLVM_PRETTY_FUNCTION, dispatch_error.AsCString(), dispatch_error);
+  }
+
+  return val;
+}
+
 #endif
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
index 3aff237ae65d5..bb866c9146640 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
@@ -52,6 +52,12 @@ class ScriptedFramePythonInterface : public 
ScriptedFrameInterface,
   StructuredData::DictionarySP GetRegisterInfo() override;
 
   std::optional<std::string> GetRegisterContext() override;
+
+  std::optional<lldb::ValueObjectListSP> GetVariables() override;
+
+  lldb::ValueObjectSP
+  GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
+                                      Status &status) override;
 };
 } // namespace lldb_private
 
diff --git 
a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
 
b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 964d213b16887..7dd74013b90f8 100644
--- 
a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ 
b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -730,3 +730,56 @@ def test_chained_frame_providers(self):
         frame3 = thread.GetFrameAtIndex(3)
         self.assertIsNotNone(frame3)
         self.assertIn("thread_func", frame3.GetFunctionName())
+
+    def test_get_values(self):
+        """Test a frame that provides values."""
+        self.build()
+        # Set the breakpoint after the variable_in_main variable exists and 
can be queried.
+        target, process, thread, bkpt = lldbutil.run_to_line_breakpoint(
+            self, lldb.SBFileSpec(self.source), 35, only_one_thread=False
+        )
+
+        # Get original frame count.
+        original_frame_count = thread.GetNumFrames()
+        self.assertGreaterEqual(
+            original_frame_count, 2, "Should have at least 2 real frames"
+        )
+
+        # Import the test frame providers.
+        script_path = os.path.join(self.getSourceDir(), 
"test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        # Register a provider that can provide variables.
+        error = lldb.SBError()
+        target.RegisterScriptedFrameProvider(
+            "test_frame_providers.ValueProvidingFrameProvider",
+            lldb.SBStructuredData(),
+            error,
+        )
+        self.assertTrue(error.Success(), f"Failed to register provider: 
{error}")
+
+        # Verify we have 1 more frame.
+        new_frame_count = thread.GetNumFrames()
+        self.assertEqual(
+            new_frame_count,
+            original_frame_count + 1,
+            "Should have original frames + 1 extra frames",
+        )
+
+        # Check that we can get variables from this frame.
+        frame0 = thread.GetFrameAtIndex(0)
+        self.assertIsNotNone(frame0)
+        # Get every variable visible at this point
+        variables = frame0.GetVariables(True, True, True, False)
+        self.assertTrue(variables.IsValid() and variables.GetSize() == 1)
+
+        # Check that we can get values from paths. `_handler_one` is a special
+        # value we provide through only our expression handler in the frame
+        # implementation.
+        one = frame0.GetValueForVariablePath("_handler_one")
+        self.assertEqual(one.unsigned, 1)
+        var = frame0.GetValueForVariablePath("variable_in_main")
+        # The names won't necessarily match, but the values should (the frame 
renames the SBValue)
+        self.assertEqual(var.unsigned, variables.GetValueAtIndex(0).unsigned)
+        varp1 = frame0.GetValueForVariablePath("variable_in_main + 1")
+        self.assertEqual(varp1.unsigned, 124)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/main.cpp 
b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp
index 0298e88e4de16..e1d346c29052b 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/main.cpp
+++ b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp
@@ -29,6 +29,10 @@ void thread_func(int thread_num) {
 int main(int argc, char **argv) {
   std::thread threads[NUM_THREADS];
 
+  // Used as an existing C++ variable we can anchor on.
+  int variable_in_main = 123;
+  (void)variable_in_main;
+
   for (int i = 0; i < NUM_THREADS; i++) {
     threads[i] = std::thread(thread_func, i);
   }
diff --git 
a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py 
b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
index 6233041f68a51..3a30e4fa96d6e 100644
--- 
a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
+++ 
b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
@@ -458,3 +458,85 @@ def get_frame_at_index(self, index):
             # Pass through input frames (shifted by 1)
             return index - 1
         return None
+
+
+class ValueProvidingFrame(ScriptedFrame):
+    """Scripted frame with a valid PC but no associated module."""
+
+    def __init__(self, thread, idx, pc, function_name, variable):
+        args = lldb.SBStructuredData()
+        super().__init__(thread, args)
+
+        self.idx = idx
+        self.pc = pc
+        self.function_name = function_name
+        self.variable = variable
+
+    def get_id(self):
+        """Return the frame index."""
+        return self.idx
+
+    def get_pc(self):
+        """Return the program counter."""
+        return self.pc
+
+    def get_function_name(self):
+        """Return the function name."""
+        return self.function_name
+
+    def is_artificial(self):
+        """Not artificial."""
+        return False
+
+    def is_hidden(self):
+        """Not hidden."""
+        return False
+
+    def get_register_context(self):
+        """No register context."""
+        return None
+
+    def get_variables(self):
+        """"""
+        out = lldb.SBValueList()
+        out.Append(self.variable)
+        return out
+
+    def get_value_for_variable_expression(self, expr, options, error: 
lldb.SBError):
+        out = lldb.SBValue()
+        if expr == "_handler_one":
+            out = self.variable.CreateValueFromExpression("_handler_one", 
"(uint32_t)1")
+        elif self.variable.name in expr:
+            out = self.variable.CreateValueFromExpression("_expr", expr)
+
+        if out.IsValid():
+            return out
+
+        error.SetErrorString(f"expression {expr} failed")
+        return None
+
+
+class ValueProvidingFrameProvider(ScriptedFrameProvider):
+    """Add a single 'value-provider' frame at the beginning."""
+
+    def __init__(self, input_frames, args):
+        super().__init__(input_frames, args)
+
+    @staticmethod
+    def get_description():
+        """Return a description of this provider."""
+        return "Add 'value-provider' frame at beginning"
+
+    def get_frame_at_index(self, index):
+        if index == 0:
+            f = self.input_frames.GetFrameAtIndex(index)
+            # Find some variable we can give to the frame.
+            variable = f.FindVariable("variable_in_main")
+            # Return synthetic "value-provider" frame
+            return ValueProvidingFrame(
+                self.thread, 0, 0xF00, "value-provider", variable
+            )
+        elif index - 1 < len(self.input_frames):
+            # Pass through input frames (shifted by 1)
+            return index - 1
+        return None

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to