[Lldb-commits] [lldb] [lldb] Fix ScriptedFrame thread member init assignment (PR #191297)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Thu Apr 9 14:42:31 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/191297
>From 45c08d7971440f239da0b13718a77149fcd3e822 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Thu, 9 Apr 2026 14:42:11 -0700
Subject: [PATCH] [lldb] Fix ScriptedFrame thread member init assignment
This patch fixes a typo in the `ScriptedFrame` base class initializer
where we used a thread id with `GetThreadByIndexID` instead of the thread
index.
This could lead to issues where derived classes wouldn't be initialized
properly, which could cause crashes down the line.
The patch addresses the issue by calling `GetThreadByID` with the thread id.
rdar://174432881
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
.../python/templates/scripted_process.py | 2 +-
.../TestScriptedFrameProvider.py | 47 +++++++++++++++
.../test_frame_providers.py | 59 +++++++++++++++++++
.../scripted_process/TestScriptedProcess.py | 30 ++++++++++
4 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py
index b6f2d18971e72..9a097fab4f803 100644
--- a/lldb/examples/python/templates/scripted_process.py
+++ b/lldb/examples/python/templates/scripted_process.py
@@ -432,7 +432,7 @@ def __init__(self, thread, args):
self.arch = triple.split("-")[0]
tid = thread.tid if isinstance(thread, ScriptedThread) else thread.id
self.originating_thread = thread
- self.thread = self.process.GetThreadByIndexID(tid)
+ self.thread = self.process.GetThreadByID(tid)
self.get_register_info()
@abstractmethod
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 8c2d8f0d5ad52..577d36f5f0799 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -129,6 +129,53 @@ def test_append_frames(self):
frame_n_plus_1 = thread.GetFrameAtIndex(new_frame_count - 1)
self.assertEqual(frame_n_plus_1.GetPC(), 0x10)
+ def test_scripted_frame_thread_member(self):
+ """Test that ScriptedFrame.thread is correctly set via GetThreadByID.
+
+ This is a regression test for a bug where ScriptedFrame.__init__ used
+ GetThreadByIndexID(tid) instead of GetThreadByID(tid). Since thread ID
+ and index ID differ, the wrong API would produce an invalid thread.
+ """
+ self.build()
+ target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+ self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
+ )
+
+ script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+ self.runCmd("command script import " + script_path)
+
+ error = lldb.SBError()
+ provider_id = target.RegisterScriptedFrameProvider(
+ "test_frame_providers.ThreadValidatingFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(error.Success(), f"Failed to register provider: {error}")
+ self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero")
+
+ # The ThreadValidatingFrame encodes thread validity in its function name.
+ frame0 = thread.GetFrameAtIndex(0)
+ func_name = frame0.GetFunctionName()
+
+ # If GetThreadByIndexID were used instead of GetThreadByID, the thread
+ # would be invalid and the function name would be "thread_INVALID".
+ self.assertNotEqual(
+ func_name,
+ "thread_INVALID",
+ "ScriptedFrame.thread should be valid "
+ "(GetThreadByID vs GetThreadByIndexID)",
+ )
+ self.assertIn("thread_valid_id_", func_name)
+
+ # Verify the encoded thread ID matches the actual thread ID.
+ expected_tid = thread.GetThreadID()
+ self.assertIn(
+ hex(expected_tid),
+ func_name,
+ f"ScriptedFrame.thread ID should match: expected {expected_tid:#x} "
+ f"in '{func_name}'",
+ )
+
def test_scripted_frame_objects(self):
"""Test that provider can return ScriptedFrame objects."""
self.build()
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 3a30e4fa96d6e..71457d3a1c227 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
@@ -460,6 +460,65 @@ def get_frame_at_index(self, index):
return None
+class ThreadValidatingFrame(ScriptedFrame):
+ """Frame that validates thread member is correctly set after init.
+
+ This catches the regression where ScriptedFrame.__init__ used
+ GetThreadByIndexID(tid) instead of GetThreadByID(tid). The thread ID
+ and index ID are different, so using the wrong API produces an invalid
+ thread. The validation result is encoded in the function name so the
+ test can verify it through the SB API.
+ """
+
+ def __init__(self, thread, idx):
+ args = lldb.SBStructuredData()
+ super().__init__(thread, args)
+ self.idx = idx
+ # Store validation results after base class init
+ self.thread_valid = self.thread is not None and self.thread.IsValid()
+ self.thread_id = self.thread.GetThreadID() if self.thread_valid else 0
+
+ def get_id(self):
+ return self.idx
+
+ def get_function_name(self):
+ # Encode validation in function name so test can verify via SB API
+ if self.thread_valid:
+ return f"thread_valid_id_{self.thread_id:#x}"
+ return "thread_INVALID"
+
+ def is_artificial(self):
+ return False
+
+ def is_hidden(self):
+ return False
+
+ def get_register_context(self):
+ return None
+
+
+class ThreadValidatingFrameProvider(ScriptedFrameProvider):
+ """Provider that validates ScriptedFrame.thread member initialization.
+
+ Uses ThreadValidatingFrame to verify that self.thread is correctly set
+ via GetThreadByID (not GetThreadByIndexID) after ScriptedFrame.__init__.
+ """
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ @staticmethod
+ def get_description():
+ return "Validate ScriptedFrame.thread member initialization"
+
+ def get_frame_at_index(self, index):
+ if index == 0:
+ return ThreadValidatingFrame(self.thread, 0)
+ elif index - 1 < len(self.input_frames):
+ return index - 1
+ return None
+
+
class ValueProvidingFrame(ScriptedFrame):
"""Scripted frame with a valid PC but no associated module."""
diff --git a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
index 5916e62c44f2e..69d93dedffd00 100644
--- a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
+++ b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
@@ -287,3 +287,33 @@ def cleanup():
self.assertTrue(frame.IsSynthetic(), "Frame is not synthetic")
pc = frame.GetPCAddress().GetLoadAddress(target_0)
self.assertEqual(pc, 0x0100001B00)
+
+ # Regression test: ScriptedFrame.__init__ used GetThreadByIndexID(tid)
+ # instead of GetThreadByID(tid). If the thread ID doesn't match any
+ # thread index, the lookup returns an invalid SBThread. Downstream,
+ # resolving the execution context from an SBFrame backed by this
+ # invalid thread yields a null Process, and calling
+ # ProcessModID::IsRunningExpression() on it crashes.
+ tid = thread.GetThreadID()
+ self.assertTrue(
+ process_0.GetThreadByID(tid).IsValid(),
+ "GetThreadByID(tid) should find the thread",
+ )
+ self.assertFalse(
+ process_0.GetThreadByIndexID(tid).IsValid(),
+ "GetThreadByIndexID(tid) should NOT find the thread "
+ "(index ID != thread ID)",
+ )
+
+ # Construct a new ScriptedFrame with the SBThread (which carries the
+ # real thread ID) and verify its thread member is valid. This exercises
+ # the SBThread branch in ScriptedFrame.__init__ where thread.id is
+ # used with GetThreadByID.
+ post_launch_frame = dummy_scripted_process.DummyScriptedFrame(
+ thread, lldb.SBStructuredData(), 42, "test_frame"
+ )
+ self.assertTrue(
+ post_launch_frame.thread.IsValid(),
+ "ScriptedFrame.thread should be valid after thread " "registration",
+ )
+ self.assertEqual(post_launch_frame.thread.GetThreadID(), tid)
More information about the lldb-commits
mailing list