diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 8830641f0abdc7..80ba9a7ae61b64 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -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. @@ -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') + 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) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-18-08-14-52.gh-issue-137894.SrkIA_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-18-08-14-52.gh-issue-137894.SrkIA_.rst new file mode 100644 index 00000000000000..55bebff0aa822e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-18-08-14-52.gh-issue-137894.SrkIA_.rst @@ -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. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 2c4bf46d85b0ba..2a6fe6cfd836d6 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -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) { @@ -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 */