Skip to content
1 change: 1 addition & 0 deletions Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ _PyFrame_InitializeSpecials(
frame->f_locals = locals;
frame->stacktop = code->co_nlocalsplus;
frame->frame_obj = NULL;
frame->previous = NULL;
frame->prev_instr = _PyCode_CODE(code) - 1;
frame->yield_offset = 0;
frame->owner = FRAME_OWNED_BY_THREAD;
Expand Down
31 changes: 31 additions & 0 deletions Lib/test/test_ctypes/test_python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,36 @@ def test_pyobject_repr(self):
self.assertEqual(repr(py_object(42)), "py_object(42)")
self.assertEqual(repr(py_object(object)), "py_object(%r)" % object)

def test_PyFrame_New_f_back(self):
"""Test that accessing `f_back` does not cause a segmentation fault on
a frame created with ctypes (GH-99110)."""
# Adapted from:
# https://naleraphael.github.io/blog/posts/devlog_create_a_builtin_frame_object/

p_memtype = POINTER(c_ulong if sizeof(c_void_p) == 8 else c_uint)
pythonapi.PyFrame_New.argtypes = (
p_memtype, # PyThreadState *tstate
p_memtype, # PyCodeObject *code
py_object, # PyObject *globals
py_object # PyObject *locals
)
pythonapi.PyFrame_New.restype = py_object # PyFrameObject*
pythonapi.PyThreadState_Get.argtypes = None
pythonapi.PyThreadState_Get.restype = p_memtype

def dummy():
pass

frame = pythonapi.PyFrame_New(
pythonapi.PyThreadState_Get(), # thread state
cast(id(dummy.__code__), p_memtype), # a code object
globals(),
locals(),
)

# The following line should not cause a segmentation fault.
assert frame.f_back is None


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Initialize frame->previous in _PyFrame_InitializeSpecials.