Skip to content

Conversation

@medismailben
Copy link
Member

This patch introduces SBFrameList, a new SBAPI class that allows iterating over stack frames lazily without calling SBThread::GetFrameAtIndex in a loop.

The new SBThread::GetFrames() method returns an SBFrameList that supports Python iteration (for frame in frame_list:), indexing (frame_list[0], frame_list[-1]), and length queries (len()).

The implementation uses StackFrameListSP as the opaque pointer, sharing the thread's underlying frame list to ensure frames are materialized on-demand.

This is particularly useful for ScriptedFrameProviders, where user scripts will be to iterate, filter, and replace frames lazily without materializing the entire stack upfront.

@llvmbot
Copy link
Member

llvmbot commented Nov 5, 2025

@llvm/pr-subscribers-lldb

Author: Med Ismail Bennani (medismailben)

Changes

This patch introduces SBFrameList, a new SBAPI class that allows iterating over stack frames lazily without calling SBThread::GetFrameAtIndex in a loop.

The new SBThread::GetFrames() method returns an SBFrameList that supports Python iteration (for frame in frame_list:), indexing (frame_list[0], frame_list[-1]), and length queries (len()).

The implementation uses StackFrameListSP as the opaque pointer, sharing the thread's underlying frame list to ensure frames are materialized on-demand.

This is particularly useful for ScriptedFrameProviders, where user scripts will be to iterate, filter, and replace frames lazily without materializing the entire stack upfront.


Patch is 21.67 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/166651.diff

18 Files Affected:

  • (added) lldb/bindings/interface/SBFrameListDocstrings.i (+13)
  • (added) lldb/bindings/interface/SBFrameListExtensions.i (+41)
  • (modified) lldb/bindings/interface/SBThreadExtensions.i (+2-1)
  • (modified) lldb/bindings/interfaces.swig (+3)
  • (modified) lldb/include/lldb/API/LLDB.h (+1)
  • (modified) lldb/include/lldb/API/SBDefines.h (+1)
  • (modified) lldb/include/lldb/API/SBFrame.h (+1)
  • (added) lldb/include/lldb/API/SBFrameList.h (+57)
  • (modified) lldb/include/lldb/API/SBStream.h (+1)
  • (modified) lldb/include/lldb/API/SBThread.h (+3)
  • (modified) lldb/include/lldb/Target/StackFrameList.h (+3)
  • (modified) lldb/include/lldb/Target/Thread.h (+2-2)
  • (modified) lldb/source/API/CMakeLists.txt (+1)
  • (added) lldb/source/API/SBFrameList.cpp (+111)
  • (modified) lldb/source/API/SBThread.cpp (+21)
  • (added) lldb/test/API/python_api/frame_list/Makefile (+3)
  • (added) lldb/test/API/python_api/frame_list/TestSBFrameList.py (+194)
  • (added) lldb/test/API/python_api/frame_list/main.cpp (+22)
diff --git a/lldb/bindings/interface/SBFrameListDocstrings.i b/lldb/bindings/interface/SBFrameListDocstrings.i
new file mode 100644
index 0000000000000..2ca10b3b4c72b
--- /dev/null
+++ b/lldb/bindings/interface/SBFrameListDocstrings.i
@@ -0,0 +1,13 @@
+%feature("docstring",
+"Represents a list of :py:class:`SBFrame` objects."
+) lldb::SBFrameList;
+
+%feature("autodoc", "GetSize(SBFrameList self) -> uint32_t") lldb::SBFrameList::GetSize;
+%feature("docstring", "
+    Returns the number of frames in the list."
+) lldb::SBFrameList::GetSize;
+
+%feature("autodoc", "GetFrameAtIndex(SBFrameList self, uint32_t idx) -> SBFrame") lldb::SBFrameList::GetFrameAtIndex;
+%feature("docstring", "
+    Returns the frame at the given index."
+) lldb::SBFrameList::GetFrameAtIndex;
\ No newline at end of file
diff --git a/lldb/bindings/interface/SBFrameListExtensions.i b/lldb/bindings/interface/SBFrameListExtensions.i
new file mode 100644
index 0000000000000..61f05add9c9a5
--- /dev/null
+++ b/lldb/bindings/interface/SBFrameListExtensions.i
@@ -0,0 +1,41 @@
+%extend lldb::SBFrameList {
+
+#ifdef SWIGPYTHON
+       %nothreadallow;
+#endif
+       std::string lldb::SBFrameList::__str__ (){
+           lldb::SBStream description;
+           if (!$self->GetDescription(description))
+               return std::string("<empty> lldb.SBFrameList()");
+           const char *desc = description.GetData();
+           size_t desc_len = description.GetSize();
+           if (desc_len > 0 && (desc[desc_len-1] == '\n' || desc[desc_len-1] == '\r'))
+               --desc_len;
+           return std::string(desc, desc_len);
+       }
+#ifdef SWIGPYTHON
+       %clearnothreadallow;
+#endif
+
+#ifdef SWIGPYTHON
+    %pythoncode %{
+        def __iter__(self):
+            '''Iterate over all frames in a lldb.SBFrameList object.'''
+            return lldb_iter(self, 'GetSize', 'GetFrameAtIndex')
+
+        def __len__(self):
+            return int(self.GetSize())
+
+        def __getitem__(self, key):
+            if type(key) is not int:
+                return None
+            if key < 0:
+                count = len(self)
+                if -count <= key < count:
+                    key %= count
+
+            frame = self.GetFrameAtIndex(key)
+            return frame if frame.IsValid() else None
+    %}
+#endif
+}
\ No newline at end of file
diff --git a/lldb/bindings/interface/SBThreadExtensions.i b/lldb/bindings/interface/SBThreadExtensions.i
index 4ec9f10b1a256..c9ae4103d7b60 100644
--- a/lldb/bindings/interface/SBThreadExtensions.i
+++ b/lldb/bindings/interface/SBThreadExtensions.i
@@ -41,7 +41,8 @@ STRING_EXTENSION_OUTSIDE(SBThread)
         def get_thread_frames(self):
             '''An accessor function that returns a list() that contains all frames in a lldb.SBThread object.'''
             frames = []
-            for frame in self:
+            frame_list = self.GetFrames()
+            for frame in frame_list:
                 frames.append(frame)
             return frames
 
diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig
index b3d44979c916c..5fe058e33e619 100644
--- a/lldb/bindings/interfaces.swig
+++ b/lldb/bindings/interfaces.swig
@@ -39,6 +39,7 @@
 %include "./interface/SBFileSpecListDocstrings.i"
 %include "./interface/SBFormatDocstrings.i"
 %include "./interface/SBFrameDocstrings.i"
+%include "./interface/SBFrameListDocstrings.i"
 %include "./interface/SBFunctionDocstrings.i"
 %include "./interface/SBHostOSDocstrings.i"
 %include "./interface/SBInstructionDocstrings.i"
@@ -119,6 +120,7 @@
 %include "lldb/API/SBFileSpecList.h"
 %include "lldb/API/SBFormat.h"
 %include "lldb/API/SBFrame.h"
+%include "lldb/API/SBFrameList.h"
 %include "lldb/API/SBFunction.h"
 %include "lldb/API/SBHostOS.h"
 %include "lldb/API/SBInstruction.h"
@@ -193,6 +195,7 @@
 %include "./interface/SBFileSpecExtensions.i"
 %include "./interface/SBFileSpecListExtensions.i"
 %include "./interface/SBFrameExtensions.i"
+%include "./interface/SBFrameListExtensions.i"
 %include "./interface/SBFunctionExtensions.i"
 %include "./interface/SBInstructionExtensions.i"
 %include "./interface/SBInstructionListExtensions.i"
diff --git a/lldb/include/lldb/API/LLDB.h b/lldb/include/lldb/API/LLDB.h
index 6485f35302a1c..6ac35bb4a364b 100644
--- a/lldb/include/lldb/API/LLDB.h
+++ b/lldb/include/lldb/API/LLDB.h
@@ -37,6 +37,7 @@
 #include "lldb/API/SBFileSpecList.h"
 #include "lldb/API/SBFormat.h"
 #include "lldb/API/SBFrame.h"
+#include "lldb/API/SBFrameList.h"
 #include "lldb/API/SBFunction.h"
 #include "lldb/API/SBHostOS.h"
 #include "lldb/API/SBInstruction.h"
diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h
index 85f6bbeea5bf9..5fcc685050c0b 100644
--- a/lldb/include/lldb/API/SBDefines.h
+++ b/lldb/include/lldb/API/SBDefines.h
@@ -76,6 +76,7 @@ class LLDB_API SBFileSpec;
 class LLDB_API SBFileSpecList;
 class LLDB_API SBFormat;
 class LLDB_API SBFrame;
+class LLDB_API SBFrameList;
 class LLDB_API SBFunction;
 class LLDB_API SBHostOS;
 class LLDB_API SBInstruction;
diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h
index 92917e57fc125..5283cdfe53faa 100644
--- a/lldb/include/lldb/API/SBFrame.h
+++ b/lldb/include/lldb/API/SBFrame.h
@@ -222,6 +222,7 @@ class LLDB_API SBFrame {
 protected:
   friend class SBBlock;
   friend class SBExecutionContext;
+  friend class SBFrameList;
   friend class SBInstruction;
   friend class SBThread;
   friend class SBValue;
diff --git a/lldb/include/lldb/API/SBFrameList.h b/lldb/include/lldb/API/SBFrameList.h
new file mode 100644
index 0000000000000..4b875f848829e
--- /dev/null
+++ b/lldb/include/lldb/API/SBFrameList.h
@@ -0,0 +1,57 @@
+//===------------------------------------------------------------*- 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_API_SBFRAMELIST_H
+#define LLDB_API_SBFRAMELIST_H
+
+#include "lldb/API/SBDefines.h"
+
+namespace lldb {
+
+class LLDB_API SBFrameList {
+public:
+  SBFrameList();
+
+  SBFrameList(const lldb::SBFrameList &rhs);
+
+  ~SBFrameList();
+
+  const lldb::SBFrameList &operator=(const lldb::SBFrameList &rhs);
+
+  explicit operator bool() const;
+
+  bool IsValid() const;
+
+  uint32_t GetSize() const;
+
+  lldb::SBFrame GetFrameAtIndex(uint32_t idx) const;
+
+  lldb::SBThread GetThread() const;
+
+  void Clear();
+
+  void Append(const lldb::SBFrame &frame);
+
+  void Append(const lldb::SBFrameList &frame_list);
+
+  bool GetDescription(lldb::SBStream &description) const;
+
+protected:
+  friend class SBThread;
+
+private:
+  SBFrameList(const lldb::StackFrameListSP &frame_list_sp);
+
+  void SetFrameList(const lldb::StackFrameListSP &frame_list_sp);
+
+  lldb::StackFrameListSP m_opaque_sp;
+};
+
+} // namespace lldb
+
+#endif // LLDB_API_SBFRAMELIST_H
diff --git a/lldb/include/lldb/API/SBStream.h b/lldb/include/lldb/API/SBStream.h
index d230da6123fb3..21f9d21e0e717 100644
--- a/lldb/include/lldb/API/SBStream.h
+++ b/lldb/include/lldb/API/SBStream.h
@@ -81,6 +81,7 @@ class LLDB_API SBStream {
   friend class SBFileSpec;
   friend class SBFileSpecList;
   friend class SBFrame;
+  friend class SBFrameList;
   friend class SBFunction;
   friend class SBInstruction;
   friend class SBInstructionList;
diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h
index e9fe5858d125e..3a78026c6687b 100644
--- a/lldb/include/lldb/API/SBThread.h
+++ b/lldb/include/lldb/API/SBThread.h
@@ -178,6 +178,8 @@ class LLDB_API SBThread {
 
   lldb::SBFrame GetFrameAtIndex(uint32_t idx);
 
+  lldb::SBFrameList GetFrames();
+
   lldb::SBFrame GetSelectedFrame();
 
   lldb::SBFrame SetSelectedFrame(uint32_t frame_idx);
@@ -236,6 +238,7 @@ class LLDB_API SBThread {
   friend class SBSaveCoreOptions;
   friend class SBExecutionContext;
   friend class SBFrame;
+  friend class SBFrameList;
   friend class SBProcess;
   friend class SBDebugger;
   friend class SBValue;
diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h
index ea9aab86b8ea1..5b0df0ddb3e29 100644
--- a/lldb/include/lldb/Target/StackFrameList.h
+++ b/lldb/include/lldb/Target/StackFrameList.h
@@ -101,6 +101,9 @@ class StackFrameList {
   /// Returns whether we have currently fetched all the frames of a stack.
   bool WereAllFramesFetched() const;
 
+  /// Get the thread associated with this frame list.
+  Thread &GetThread() const { return m_thread; }
+
 protected:
   friend class Thread;
   friend class ScriptedThread;
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index 688c056da2633..841f80cd1b1eb 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -1295,6 +1295,8 @@ class Thread : public std::enable_shared_from_this<Thread>,
   ///     an empty std::optional is returned in that case.
   std::optional<lldb::addr_t> GetPreviousFrameZeroPC();
 
+  lldb::StackFrameListSP GetStackFrameList();
+
 protected:
   friend class ThreadPlan;
   friend class ThreadList;
@@ -1336,8 +1338,6 @@ class Thread : public std::enable_shared_from_this<Thread>,
     return StructuredData::ObjectSP();
   }
 
-  lldb::StackFrameListSP GetStackFrameList();
-
   void SetTemporaryResumeState(lldb::StateType new_state) {
     m_temporary_resume_state = new_state;
   }
diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt
index ce59ee505cd3d..ac47580d60840 100644
--- a/lldb/source/API/CMakeLists.txt
+++ b/lldb/source/API/CMakeLists.txt
@@ -69,6 +69,7 @@ add_lldb_library(liblldb SHARED ${option_framework}
   SBFileSpecList.cpp
   SBFormat.cpp
   SBFrame.cpp
+  SBFrameList.cpp
   SBFunction.cpp
   SBHostOS.cpp
   SBInstruction.cpp
diff --git a/lldb/source/API/SBFrameList.cpp b/lldb/source/API/SBFrameList.cpp
new file mode 100644
index 0000000000000..77f58e3ffc080
--- /dev/null
+++ b/lldb/source/API/SBFrameList.cpp
@@ -0,0 +1,111 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/API/SBFrameList.h"
+#include "lldb/API/SBFrame.h"
+#include "lldb/API/SBStream.h"
+#include "lldb/API/SBThread.h"
+#include "lldb/Target/StackFrameList.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Utility/Instrumentation.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+SBFrameList::SBFrameList() : m_opaque_sp() { LLDB_INSTRUMENT_VA(this); }
+
+SBFrameList::SBFrameList(const SBFrameList &rhs)
+    : m_opaque_sp(rhs.m_opaque_sp) {
+  LLDB_INSTRUMENT_VA(this, rhs);
+}
+
+SBFrameList::~SBFrameList() = default;
+
+const SBFrameList &SBFrameList::operator=(const SBFrameList &rhs) {
+  LLDB_INSTRUMENT_VA(this, rhs);
+
+  if (this != &rhs)
+    m_opaque_sp = rhs.m_opaque_sp;
+  return *this;
+}
+
+SBFrameList::SBFrameList(const lldb::StackFrameListSP &frame_list_sp)
+    : m_opaque_sp(frame_list_sp) {}
+
+void SBFrameList::SetFrameList(const lldb::StackFrameListSP &frame_list_sp) {
+  m_opaque_sp = frame_list_sp;
+}
+
+SBFrameList::operator bool() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  return m_opaque_sp.get() != nullptr;
+}
+
+bool SBFrameList::IsValid() const {
+  LLDB_INSTRUMENT_VA(this);
+  return this->operator bool();
+}
+
+uint32_t SBFrameList::GetSize() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  if (m_opaque_sp)
+    return m_opaque_sp->GetNumFrames();
+  return 0;
+}
+
+SBFrame SBFrameList::GetFrameAtIndex(uint32_t idx) const {
+  LLDB_INSTRUMENT_VA(this, idx);
+
+  SBFrame sb_frame;
+  if (m_opaque_sp)
+    sb_frame.SetFrameSP(m_opaque_sp->GetFrameAtIndex(idx));
+  return sb_frame;
+}
+
+SBThread SBFrameList::GetThread() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  SBThread sb_thread;
+  if (m_opaque_sp)
+    sb_thread.SetThread(m_opaque_sp->GetThread().shared_from_this());
+  return sb_thread;
+}
+
+void SBFrameList::Clear() {
+  LLDB_INSTRUMENT_VA(this);
+
+  if (m_opaque_sp)
+    m_opaque_sp->Clear();
+}
+
+void SBFrameList::Append(const SBFrame &frame) {
+  LLDB_INSTRUMENT_VA(this, frame);
+
+  // Note: StackFrameList doesn't have an Append method, so this is a no-op
+  // This method is kept for API consistency with other SB*List classes
+}
+
+void SBFrameList::Append(const SBFrameList &frame_list) {
+  LLDB_INSTRUMENT_VA(this, frame_list);
+
+  // Note: StackFrameList doesn't have an Append method, so this is a no-op
+  // This method is kept for API consistency with other SB*List classes
+}
+
+bool SBFrameList::GetDescription(SBStream &description) const {
+  LLDB_INSTRUMENT_VA(this, description);
+
+  Stream &strm = description.ref();
+  if (m_opaque_sp) {
+    m_opaque_sp->Dump(&strm);
+    return true;
+  }
+  return false;
+}
diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp
index 4e4aa48bc9a2e..2351d787c74e9 100644
--- a/lldb/source/API/SBThread.cpp
+++ b/lldb/source/API/SBThread.cpp
@@ -14,6 +14,7 @@
 #include "lldb/API/SBFileSpec.h"
 #include "lldb/API/SBFormat.h"
 #include "lldb/API/SBFrame.h"
+#include "lldb/API/SBFrameList.h"
 #include "lldb/API/SBProcess.h"
 #include "lldb/API/SBStream.h"
 #include "lldb/API/SBStructuredData.h"
@@ -1079,6 +1080,26 @@ SBFrame SBThread::GetFrameAtIndex(uint32_t idx) {
   return sb_frame;
 }
 
+lldb::SBFrameList SBThread::GetFrames() {
+  LLDB_INSTRUMENT_VA(this);
+
+  SBFrameList sb_frame_list;
+  llvm::Expected<StoppedExecutionContext> exe_ctx =
+      GetStoppedExecutionContext(m_opaque_sp);
+  if (!exe_ctx) {
+    LLDB_LOG_ERROR(GetLog(LLDBLog::API), exe_ctx.takeError(), "{0}");
+    return SBFrameList();
+  }
+
+  if (exe_ctx->HasThreadScope()) {
+    StackFrameListSP frame_list_sp =
+        exe_ctx->GetThreadPtr()->GetStackFrameList();
+    sb_frame_list.SetFrameList(frame_list_sp);
+  }
+
+  return sb_frame_list;
+}
+
 lldb::SBFrame SBThread::GetSelectedFrame() {
   LLDB_INSTRUMENT_VA(this);
 
diff --git a/lldb/test/API/python_api/frame_list/Makefile b/lldb/test/API/python_api/frame_list/Makefile
new file mode 100644
index 0000000000000..2bb9ce046a907
--- /dev/null
+++ b/lldb/test/API/python_api/frame_list/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
\ No newline at end of file
diff --git a/lldb/test/API/python_api/frame_list/TestSBFrameList.py b/lldb/test/API/python_api/frame_list/TestSBFrameList.py
new file mode 100644
index 0000000000000..f348ce492e547
--- /dev/null
+++ b/lldb/test/API/python_api/frame_list/TestSBFrameList.py
@@ -0,0 +1,194 @@
+"""
+Test SBFrameList API.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class FrameListAPITestCase(TestBase):
+    def test_frame_list_api(self):
+        """Test SBThread.GetFrames() returns a valid SBFrameList."""
+        self.build()
+        self.frame_list_api()
+
+    def test_frame_list_iterator(self):
+        """Test SBFrameList iterator functionality."""
+        self.build()
+        self.frame_list_iterator()
+
+    def test_frame_list_indexing(self):
+        """Test SBFrameList indexing and length."""
+        self.build()
+        self.frame_list_indexing()
+
+    def test_frame_list_get_thread(self):
+        """Test SBFrameList.GetThread() returns correct thread."""
+        self.build()
+        self.frame_list_get_thread()
+
+    def setUp(self):
+        TestBase.setUp(self)
+        self.main_source = "main.cpp"
+
+    def frame_list_api(self):
+        """Test SBThread.GetFrames() returns a valid SBFrameList."""
+        exe = self.getBuildArtifact("a.out")
+
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Set break point at this line", lldb.SBFileSpec(self.main_source)
+        )
+
+        self.assertTrue(
+            thread.IsValid(), "There should be a thread stopped due to breakpoint"
+        )
+
+        # Test GetFrames() returns a valid SBFrameList
+        frame_list = thread.GetFrames()
+        self.assertTrue(frame_list.IsValid(), "Frame list should be valid")
+        self.assertGreater(
+            frame_list.GetSize(), 0, "Frame list should have at least one frame"
+        )
+
+        # Verify frame list size matches thread frame count
+        self.assertEqual(
+            frame_list.GetSize(),
+            thread.GetNumFrames(),
+            "Frame list size should match thread frame count",
+        )
+
+        # Verify frames are the same
+        for i in range(frame_list.GetSize()):
+            frame_from_list = frame_list.GetFrameAtIndex(i)
+            frame_from_thread = thread.GetFrameAtIndex(i)
+            self.assertTrue(
+                frame_from_list.IsValid(), f"Frame {i} from list should be valid"
+            )
+            self.assertEqual(
+                frame_from_list.GetPC(),
+                frame_from_thread.GetPC(),
+                f"Frame {i} PC should match",
+            )
+
+    def frame_list_iterator(self):
+        """Test SBFrameList iterator functionality."""
+        exe = self.getBuildArtifact("a.out")
+
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Set break point at this line", lldb.SBFileSpec(self.main_source)
+        )
+
+        self.assertTrue(
+            thread.IsValid(), "There should be a thread stopped due to breakpoint"
+        )
+
+        frame_list = thread.GetFrames()
+
+        # Test iteration
+        frame_count = 0
+        for frame in frame_list:
+            self.assertTrue(frame.IsValid(), "Each frame should be valid")
+            frame_count += 1
+
+        self.assertEqual(
+            frame_count,
+            frame_list.GetSize(),
+            "Iterator should visit all frames",
+        )
+
+        # Test that we can iterate multiple times
+        second_count = 0
+        for frame in frame_list:
+            second_count += 1
+
+        self.assertEqual(
+            frame_count, second_count, "Should be able to iterate multiple times"
+        )
+
+    def frame_list_indexing(self):
+        """Test SBFrameList indexing and length."""
+        exe = self.getBuildArtifact("a.out")
+
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Set break point at this line", lldb.SBFileSpec(self.main_source)
+        )
+
+        self.assertTrue(
+            thread.IsValid(), "There should be a thread stopped due to breakpoint"
+        )
+
+        frame_list = thread.GetFrames()
+
+        # Test len()
+        self.assertEqual(
+            len(frame_list), frame_list.GetSize(), "len() should return frame count"
+        )
+
+        # Test positive indexing
+        first_frame = frame_list[0]
+        self.assertTrue(first_frame.IsValid(), "First frame should be valid")
+        self.assertEqual(
+            first_frame.GetPC(),
+            thread.GetFrameAtIndex(0).GetPC(),
+            "Indexed frame should match",
+        )
+
+        # Test negative indexing
+        if len(frame_list) > 0:
+            last_frame = frame_list[-1]
+            self.assertTrue(last_frame.IsValid(), "Last frame should be valid")
+            self.assertEqual(
+                last_frame.GetPC(),
+                thread.GetFrameAtIndex(len(frame_list) - 1).GetPC(),
+                "Negative indexing should work",
+            )
+
+        # Test out of bounds returns None
+        out_of_bounds = frame_list[10000]
+        self.assertIsNone(out_of_bounds, "Out of bounds index should return None")
+
+        # Test bool conversion
+        self.assertTrue(bool(frame_list), "Non-empty frame list should be truthy")
+
+        # Test Clear()
+        frame_list.Clear()
+        # Note: Clear() clears the underlying StackFrameList cache,
+        # but the frame list object itself should still be valid
+        self.assertTrue(
+            frame_list.IsValid(), "Frame list should still be valid after Clear()"
+        )
+
+    def frame_list_get_thread(self):
+        """Test SBFrameList.GetThread() returns correct thr...
[truncated]

@medismailben medismailben force-pushed the sbframelist branch 3 times, most recently from aa9c49a to 244ae1a Compare November 5, 2025 23:51
Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM modulo missing newlines.

This patch introduces `SBFrameList`, a new SBAPI class that allows
iterating over stack frames lazily without calling
`SBThread::GetFrameAtIndex` in a loop.

The new `SBThread::GetFrames()` method returns an `SBFrameList` that
supports Python iteration (`for frame in frame_list:`), indexing
(`frame_list[0]`, `frame_list[-1]`), and length queries (`len()`).

The implementation uses `StackFrameListSP` as the opaque pointer,
sharing the thread's underlying frame list to ensure frames are
materialized on-demand.

This is particularly useful for ScriptedFrameProviders, where user
scripts can now iterate, filter, and replace frames lazily without
materializing the entire stack upfront.

Signed-off-by: Med Ismail Bennani <[email protected]>
@medismailben medismailben merged commit d584d00 into llvm:main Nov 6, 2025
5 of 9 checks passed
medismailben added a commit to medismailben/llvm-project that referenced this pull request Nov 11, 2025
This patch introduces `SBFrameList`, a new SBAPI class that allows
iterating over stack frames lazily without calling
`SBThread::GetFrameAtIndex` in a loop.

The new `SBThread::GetFrames()` method returns an `SBFrameList` that
supports Python iteration (`for frame in frame_list:`), indexing
(`frame_list[0]`, `frame_list[-1]`), and length queries (`len()`).

The implementation uses `StackFrameListSP` as the opaque pointer,
sharing the thread's underlying frame list to ensure frames are
materialized on-demand.

This is particularly useful for ScriptedFrameProviders, where user
scripts will be to iterate, filter, and replace frames lazily without
materializing the entire stack upfront.

Signed-off-by: Med Ismail Bennani <[email protected]>
(cherry picked from commit d584d00)
medismailben added a commit to medismailben/llvm-project that referenced this pull request Nov 30, 2025
This patch introduces `SBFrameList`, a new SBAPI class that allows
iterating over stack frames lazily without calling
`SBThread::GetFrameAtIndex` in a loop.

The new `SBThread::GetFrames()` method returns an `SBFrameList` that
supports Python iteration (`for frame in frame_list:`), indexing
(`frame_list[0]`, `frame_list[-1]`), and length queries (`len()`).

The implementation uses `StackFrameListSP` as the opaque pointer,
sharing the thread's underlying frame list to ensure frames are
materialized on-demand.

This is particularly useful for ScriptedFrameProviders, where user
scripts will be to iterate, filter, and replace frames lazily without
materializing the entire stack upfront.

Signed-off-by: Med Ismail Bennani <[email protected]>
(cherry picked from commit d584d00)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants