[Lldb-commits] [lldb] [lldb] Introduce SBFrameList for lazy frame iteration (PR #166651)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Wed Nov 5 16:01:25 PST 2025
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/166651
>From 53a6cf72a8cda4f277694b90202af822b2401ad3 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Wed, 5 Nov 2025 15:52:32 -0800
Subject: [PATCH] [lldb] Introduce SBFrameList for lazy frame iteration
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 <ismail at bennani.ma>
---
.../interface/SBFrameListExtensions.i | 41 ++++
lldb/bindings/interface/SBThreadExtensions.i | 3 +-
lldb/bindings/interfaces.swig | 2 +
lldb/include/lldb/API/LLDB.h | 1 +
lldb/include/lldb/API/SBDefines.h | 1 +
lldb/include/lldb/API/SBFrame.h | 1 +
lldb/include/lldb/API/SBFrameList.h | 82 ++++++++
lldb/include/lldb/API/SBStream.h | 1 +
lldb/include/lldb/API/SBThread.h | 3 +
lldb/include/lldb/Target/StackFrameList.h | 3 +
lldb/include/lldb/Target/Thread.h | 4 +-
lldb/source/API/CMakeLists.txt | 1 +
lldb/source/API/SBFrameList.cpp | 97 +++++++++
lldb/source/API/SBThread.cpp | 21 ++
lldb/test/API/python_api/frame_list/Makefile | 3 +
.../python_api/frame_list/TestSBFrameList.py | 194 ++++++++++++++++++
lldb/test/API/python_api/frame_list/main.cpp | 22 ++
17 files changed, 477 insertions(+), 3 deletions(-)
create mode 100644 lldb/bindings/interface/SBFrameListExtensions.i
create mode 100644 lldb/include/lldb/API/SBFrameList.h
create mode 100644 lldb/source/API/SBFrameList.cpp
create mode 100644 lldb/test/API/python_api/frame_list/Makefile
create mode 100644 lldb/test/API/python_api/frame_list/TestSBFrameList.py
create mode 100644 lldb/test/API/python_api/frame_list/main.cpp
diff --git a/lldb/bindings/interface/SBFrameListExtensions.i b/lldb/bindings/interface/SBFrameListExtensions.i
new file mode 100644
index 0000000000000..1c6ac8d50a54c
--- /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
+}
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..fddbedf02e835 100644
--- a/lldb/bindings/interfaces.swig
+++ b/lldb/bindings/interfaces.swig
@@ -119,6 +119,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 +194,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..dba1c1de5d191
--- /dev/null
+++ b/lldb/include/lldb/API/SBFrameList.h
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 {
+
+/// Represents a list of SBFrame objects.
+///
+/// SBFrameList provides a way to iterate over stack frames lazily,
+/// materializing frames on-demand as they are accessed. This is more
+/// efficient than eagerly creating all frames upfront.
+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;
+
+ /// Returns the number of frames in the list.
+ uint32_t GetSize() const;
+
+ /// Returns the frame at the given index.
+ ///
+ /// \param[in] idx
+ /// The index of the frame to retrieve (0-based).
+ ///
+ /// \return
+ /// An SBFrame object for the frame at the specified index.
+ /// Returns an invalid SBFrame if idx is out of range.
+ lldb::SBFrame GetFrameAtIndex(uint32_t idx) const;
+
+ /// Get the thread associated with this frame list.
+ ///
+ /// \return
+ /// An SBThread object representing the thread.
+ lldb::SBThread GetThread() const;
+
+ /// Clear all frames from this list.
+ void Clear();
+
+ /// Get a description of this frame list.
+ ///
+ /// \param[in] description
+ /// The stream to write the description to.
+ ///
+ /// \return
+ /// True if the description was successfully written.
+ 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);
+
+ // This needs to be a shared_ptr since an SBFrameList can be passed to
+ // scripting affordances like ScriptedFrameProviders but also out of
+ // convenience because Thread::GetStackFrameList returns a StackFrameListSP.
+ 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..d5fa955c10f70
--- /dev/null
+++ b/lldb/source/API/SBFrameList.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// 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();
+}
+
+bool SBFrameList::GetDescription(SBStream &description) const {
+ LLDB_INSTRUMENT_VA(this, description);
+
+ if (!m_opaque_sp)
+ return false;
+
+ Stream &strm = description.ref();
+ m_opaque_sp->Dump(&strm);
+ return true;
+}
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..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/python_api/frame_list/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
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 thread."""
+ 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()
+ self.assertTrue(frame_list.IsValid(), "Frame list should be valid")
+
+ # Test GetThread() returns the correct thread
+ thread_from_list = frame_list.GetThread()
+ self.assertTrue(
+ thread_from_list.IsValid(), "Thread from frame list should be valid"
+ )
+ self.assertEqual(
+ thread_from_list.GetThreadID(),
+ thread.GetThreadID(),
+ "Frame list should return the correct thread",
+ )
+
+ # Verify it's the same thread object
+ self.assertEqual(
+ thread_from_list.GetProcess().GetProcessID(),
+ thread.GetProcess().GetProcessID(),
+ "Thread should belong to same process",
+ )
diff --git a/lldb/test/API/python_api/frame_list/main.cpp b/lldb/test/API/python_api/frame_list/main.cpp
new file mode 100644
index 0000000000000..e39944654a23e
--- /dev/null
+++ b/lldb/test/API/python_api/frame_list/main.cpp
@@ -0,0 +1,22 @@
+#include <stdio.h>
+
+int c(int val) {
+ // Set break point at this line
+ return val + 3;
+}
+
+int b(int val) {
+ int result = c(val);
+ return result;
+}
+
+int a(int val) {
+ int result = b(val);
+ return result;
+}
+
+int main() {
+ int result = a(1);
+ printf("Result: %d\n", result);
+ return 0;
+}
More information about the lldb-commits
mailing list