From 0cb2754a0b4bf55f6e6330bf576b5081a2fcbec3 Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sun, 31 May 2026 12:49:49 -0400 Subject: [PATCH 1/5] gh-142732: Prevent reentrancy in `functools.zip_longest` Adds a check in `functools.zip_longest` that raises a `ValueError` when a reentrancy is detected. --- Lib/test/test_itertools.py | 19 +++++++++++++++++++ Modules/itertoolsmodule.c | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index cf579d4da4e0dfb..c23a9bff8e9ff95 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/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 68ac810eaad237f..f9b2948bcf52b01 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -3785,6 +3785,7 @@ typedef struct { PyObject *ittuple; /* tuple of iterators */ PyObject *result; PyObject *fillvalue; + int 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,6 +3894,12 @@ zip_longest_next_lock_held(PyObject *op) PyObject *item; PyObject *olditem; + if (lz->running == 1) { + PyErr_SetString(PyExc_ValueError, "zip_logest already executing"); + return NULL; + } + lz->running = 1; + if (tuplesize == 0) return NULL; if (lz->numactive == 0) @@ -3950,6 +3958,7 @@ zip_longest_next_lock_held(PyObject *op) PyTuple_SET_ITEM(result, i, item); } } + lz->running = 0; return result; } From f6706b595d95d8dd87237077c2d38779f92723e4 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 16:58:16 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst 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 000000000000000..e76fa0b49b3c891 --- /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:`functools.zip_longest` raises an :exc:`ValueError` when a reentrancy is detected. From 5a4f05a650ee37e307d3f93bddc7cfcf21d64e55 Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sun, 31 May 2026 13:03:37 -0400 Subject: [PATCH 3/5] update news --- .../2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index e76fa0b49b3c891..9cd38a5675edf70 100644 --- 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 @@ -1 +1 @@ -Now :func:`functools.zip_longest` raises an :exc:`ValueError` when a reentrancy is detected. +Now :func:`itertools.zip_longest` raises an :exc:`ValueError` when a reentrancy is detected. From 54c51881a841a69f70a554768edabf53126bc8ad Mon Sep 17 00:00:00 2001 From: Brij <97006829+bkap123@users.noreply.github.com> Date: Sun, 31 May 2026 14:17:14 -0400 Subject: [PATCH 4/5] Update logic --- Modules/itertoolsmodule.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index f9b2948bcf52b01..ba560d3cfc5d0bf 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -3785,7 +3785,7 @@ typedef struct { PyObject *ittuple; /* tuple of iterators */ PyObject *result; PyObject *fillvalue; - int running; + uint8_t running; } ziplongestobject; #define ziplongestobject_CAST(op) ((ziplongestobject *)(op)) @@ -3895,15 +3895,20 @@ zip_longest_next_lock_held(PyObject *op) PyObject *olditem; if (lz->running == 1) { - PyErr_SetString(PyExc_ValueError, "zip_logest already executing"); + PyErr_SetString(PyExc_ValueError, + "zip_logest() iterator already executing"); return NULL; } lz->running = 1; - if (tuplesize == 0) - return NULL; - if (lz->numactive == 0) - return NULL; + 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++) { @@ -3917,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); @@ -3934,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) { @@ -3947,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); @@ -3958,6 +3967,7 @@ zip_longest_next_lock_held(PyObject *op) PyTuple_SET_ITEM(result, i, item); } } +done: lz->running = 0; return result; } From 0fda8621697ee4ec8bc4547446951991f63db0cb Mon Sep 17 00:00:00 2001 From: Brij Kapadia <97006829+brijkapadia@users.noreply.github.com> Date: Sun, 31 May 2026 15:28:18 -0400 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Pieter Eendebak --- .../2026-05-31-16-58-14.gh-issue-142732.6Q80QA.rst | 2 +- Modules/itertoolsmodule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index 9cd38a5675edf70..a8f70c3cf08e7f9 100644 --- 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 @@ -1 +1 @@ -Now :func:`itertools.zip_longest` raises an :exc:`ValueError` when a reentrancy is detected. +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 ba560d3cfc5d0bf..a2a1cbfa08a544a 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -3896,7 +3896,7 @@ zip_longest_next_lock_held(PyObject *op) if (lz->running == 1) { PyErr_SetString(PyExc_ValueError, - "zip_logest() iterator already executing"); + "zip_longest() iterator already executing"); return NULL; } lz->running = 1;