aboutsummaryrefslogtreecommitdiff
path: root/lldb/test/API/python_api
diff options
context:
space:
mode:
authorMed Ismail Bennani <ismail@bennani.ma>2025-11-05 16:02:02 -0800
committerGitHub <noreply@github.com>2025-11-05 16:02:02 -0800
commitd584d00ed250e547c9910e0a93b7f9d07f2e71c3 (patch)
tree661d4e93699403290f2528bb9f6c8cb5abb64427 /lldb/test/API/python_api
parent8321eaa037b994f22351af67a3e7d8bd4a54ae0c (diff)
downloadllvm-d584d00ed250e547c9910e0a93b7f9d07f2e71c3.tar.gz
llvm-d584d00ed250e547c9910e0a93b7f9d07f2e71c3.tar.bz2
llvm-d584d00ed250e547c9910e0a93b7f9d07f2e71c3.zip
[lldb] Introduce SBFrameList for lazy frame iteration (#166651)
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 <ismail@bennani.ma>
Diffstat (limited to 'lldb/test/API/python_api')
-rw-r--r--lldb/test/API/python_api/frame_list/Makefile3
-rw-r--r--lldb/test/API/python_api/frame_list/TestSBFrameList.py194
-rw-r--r--lldb/test/API/python_api/frame_list/main.cpp22
3 files changed, 219 insertions, 0 deletions
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 000000000000..99998b20bcb0
--- /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 000000000000..f348ce492e54
--- /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 000000000000..e39944654a23
--- /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;
+}