Skip to content
15 changes: 14 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,6 @@ def test_filter_pickle(self):

@support.skip_wasi_stack_overflow()
@support.skip_emscripten_stack_overflow()
@support.requires_resource('cpu')
def test_filter_dealloc(self):
# Tests recursive deallocation of nested filter objects using the
# thrashcan mechanism. See gh-102356 for more details.
Expand All @@ -1131,6 +1130,20 @@ def test_filter_dealloc(self):
i = filter(bool, i)
del i
gc.collect()
6
@support.skip_wasi_stack_overflow()
@support.skip_emscripten_stack_overflow()
@support.requires_resource('cpu')
Copy link
Member

@picnixz picnixz Aug 18, 2025

Choose a reason for hiding this comment

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

Does it really need a CPU resource? (or maybe it's because of i = filter(bool, i)? how long does the test take? (seconds or minutes?)

def test_filter_deep_nesting_recursion_error(self):
# gh-137894: Test that deeply nested filter() iterator chains
# raise RecursionError instead of causing segmentation fault.
# This verifies that the tp_clear method prevents stack overflow
# during garbage collection of cyclic references.
i = filter(bool, range(1000000))
for _ in range(100000):
i = filter(bool, i)

self.assertRaises(RecursionError, list, i)

def test_getattr(self):
self.assertTrue(getattr(sys, 'stdout') is sys.stdout)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix segmentation fault in deeply nested :func:`filter` iterator chains.
Deeply nested filter iterators now properly raise :exc:`RecursionError`
instead of crashing the interpreter.
11 changes: 10 additions & 1 deletion Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,15 @@ filter_traverse(PyObject *self, visitproc visit, void *arg)
return 0;
}

static int
filter_clear(PyObject *self)
{
filterobject *lz = _filterobject_CAST(self);
Py_CLEAR(lz->it);
Py_CLEAR(lz->func);
return 0;
}

static PyObject *
filter_next(PyObject *self)
{
Expand Down Expand Up @@ -661,7 +670,7 @@ PyTypeObject PyFilter_Type = {
Py_TPFLAGS_BASETYPE, /* tp_flags */
filter_doc, /* tp_doc */
filter_traverse, /* tp_traverse */
0, /* tp_clear */
filter_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
Expand Down
Loading