diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index cf579d4da4e0df..c23a9bff8e9ff9 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -2489,6 +2489,25 @@ def __eq__(self, other): for j in range(2): next(g, None) # shouldn't crash + def test_zip_longest_reentrancy(self): + class Reenter: + def __iter__(self): + return self + + def __next__(self): + z = self.zip_longest + if z is None: + raise StopIteration + self.zip_longest = None + next(z, None) + raise StopIteration + + driver = Reenter() + driver.zip_longest = itertools.zip_longest(itertools.chain(driver), itertools.repeat(None)) + + with self.assertRaises(ValueError): + next(driver.zip_longest) + class SubclassWithKwargsTest(unittest.TestCase): def test_keywords_in_subclass(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst new file mode 100644 index 00000000000000..a8f70c3cf08e7f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst @@ -0,0 +1 @@ +Now :func:`itertools.zip_longest` raises a :exc:`ValueError` when a reentrancy is detected. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 68ac810eaad237..a2a1cbfa08a544 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -3785,6 +3785,7 @@ typedef struct { PyObject *ittuple; /* tuple of iterators */ PyObject *result; PyObject *fillvalue; + uint8_t running; } ziplongestobject; #define ziplongestobject_CAST(op) ((ziplongestobject *)(op)) @@ -3854,6 +3855,7 @@ zip_longest_new(PyTypeObject *type, PyObject *args, PyObject *kwds) lz->numactive = tuplesize; lz->result = result; lz->fillvalue = Py_NewRef(fillvalue); + lz->running = 0; return (PyObject *)lz; } @@ -3892,10 +3894,21 @@ zip_longest_next_lock_held(PyObject *op) PyObject *item; PyObject *olditem; - if (tuplesize == 0) - return NULL; - if (lz->numactive == 0) + if (lz->running == 1) { + PyErr_SetString(PyExc_ValueError, + "zip_longest() iterator already executing"); return NULL; + } + lz->running = 1; + + if (tuplesize == 0) { + result = NULL; + goto done; + } + if (lz->numactive == 0) { + result = NULL; + goto done; + } if (_PyObject_IsUniquelyReferenced(result)) { Py_INCREF(result); for (i=0 ; i < tuplesize ; i++) { @@ -3909,7 +3922,8 @@ zip_longest_next_lock_held(PyObject *op) if (lz->numactive == 0 || PyErr_Occurred()) { lz->numactive = 0; Py_DECREF(result); - return NULL; + result = NULL; + goto done; } else { item = Py_NewRef(lz->fillvalue); PyTuple_SET_ITEM(lz->ittuple, i, NULL); @@ -3926,8 +3940,10 @@ zip_longest_next_lock_held(PyObject *op) _PyTuple_Recycle(result); } else { result = PyTuple_New(tuplesize); - if (result == NULL) - return NULL; + if (result == NULL) { + result = NULL; + goto done; + } for (i=0 ; i < tuplesize ; i++) { it = PyTuple_GET_ITEM(lz->ittuple, i); if (it == NULL) { @@ -3939,7 +3955,8 @@ zip_longest_next_lock_held(PyObject *op) if (lz->numactive == 0 || PyErr_Occurred()) { lz->numactive = 0; Py_DECREF(result); - return NULL; + result = NULL; + goto done; } else { item = Py_NewRef(lz->fillvalue); PyTuple_SET_ITEM(lz->ittuple, i, NULL); @@ -3950,6 +3967,8 @@ zip_longest_next_lock_held(PyObject *op) PyTuple_SET_ITEM(result, i, item); } } +done: + lz->running = 0; return result; }