Skip to content
16 changes: 16 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,22 @@ def test_filter_dealloc(self):
del i
gc.collect()

@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)

# Should raise RecursionError, not segmentation fault
with self.assertRaises(RecursionError):
list(i)

def test_getattr(self):
self.assertTrue(getattr(sys, 'stdout') is sys.stdout)
self.assertRaises(TypeError, getattr)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fix segmentation fault in deeply nested :func:`filter` iterator chains by
adding a ``tp_clear`` method to the filter object. Previously, creating
thousands of nested filter iterators and then consuming them would cause
a stack overflow during garbage collection, leading to interpreter crashes.
Now such cases properly raise :exc:`RecursionError` instead of crashing
the interpreter.
12 changes: 10 additions & 2 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,14 @@ filter_traverse(PyObject *self, visitproc visit, void *arg)
return 0;
}

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

static PyObject *
filter_next(PyObject *self)
{
Expand Down Expand Up @@ -660,8 +668,8 @@ PyTypeObject PyFilter_Type = {
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE, /* tp_flags */
filter_doc, /* tp_doc */
filter_traverse, /* tp_traverse */
0, /* tp_clear */
(traverseproc)filter_traverse, /* tp_traverse */
(inquiry)filter_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
Expand Down
Loading