diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 1d2c105ac047e1..e09135875692a3 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1432,6 +1432,14 @@ def badfunc(x): raise RuntimeError self.assertRaises(RuntimeError, list, map(badfunc, range(5))) + it = [] + for _ in range(100_000): + it = map(int, it) + with self.assertRaisesRegex( + RecursionError, r"Stack overflow .* while iterating" + ): + list(it) + def test_map_pickle(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): m1 = map(map_char, "Is this the real life?") diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-12-14-08.gh-issue-103503.SZni1r.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-12-14-08.gh-issue-103503.SZni1r.rst new file mode 100644 index 00000000000000..2095d4d4b66eb5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-15-12-14-08.gh-issue-103503.SZni1r.rst @@ -0,0 +1,2 @@ +Deeply nested ``map()`` iterators now raise ``RecursionError`` instead of +causing a stack overflow. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index cbe59c8883d5a5..4d7f639167509c 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1572,11 +1572,16 @@ map_next(PyObject *self) { mapobject *lz = _mapobject_CAST(self); Py_ssize_t i; + Py_ssize_t nargs = 0; PyObject *small_stack[_PY_FASTCALL_SMALL_STACK]; - PyObject **stack; + PyObject **stack = NULL; PyObject *result = NULL; PyThreadState *tstate = _PyThreadState_GET(); + if (_Py_EnterRecursiveCallTstate(tstate, " while calling map()")) { + return NULL; + } + const Py_ssize_t niters = PyTuple_GET_SIZE(lz->iters); if (niters <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) { stack = small_stack; @@ -1585,11 +1590,9 @@ map_next(PyObject *self) stack = PyMem_Malloc(niters * sizeof(stack[0])); if (stack == NULL) { _PyErr_NoMemory(tstate); - return NULL; + goto exit; } } - - Py_ssize_t nargs = 0; for (i = 0; i < niters; i++) { PyObject *it = PyTuple_GET_ITEM(lz->iters, i); PyObject *val = Py_TYPE(it)->tp_iternext(it); @@ -1652,9 +1655,10 @@ map_next(PyObject *self) for (i = 0; i < nargs; i++) { Py_DECREF(stack[i]); } - if (stack != small_stack) { + if (stack != NULL && stack != small_stack) { PyMem_Free(stack); } + _Py_LeaveRecursiveCallTstate(tstate); return result; }