https://github.com/python/cpython/commit/fc7a188fe70a7b98696b4fcee8db9eb8398aeb7b
commit: fc7a188fe70a7b98696b4fcee8db9eb8398aeb7b
branch: main
author: Ma Yukun <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-04-02T19:04:58+05:30
summary:
gh-146613: Fix re-entrant use-after-free in `itertools._grouper` (#147962)
files:
A Misc/NEWS.d/next/Library/2026-04-01-11-05-36.gh-issue-146613.GzjUFK.rst
M Lib/test/test_itertools.py
M Modules/itertoolsmodule.c
diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py
index dc64288085fa74..cf579d4da4e0df 100644
--- a/Lib/test/test_itertools.py
+++ b/Lib/test/test_itertools.py
@@ -754,6 +754,38 @@ def keys():
next(g)
next(g) # must pass with address sanitizer
+ def test_grouper_reentrant_eq_does_not_crash(self):
+ # regression test for gh-146613
+ grouper_iter = None
+
+ class Key:
+ __hash__ = None
+
+ def __init__(self, do_advance):
+ self.do_advance = do_advance
+
+ def __eq__(self, other):
+ nonlocal grouper_iter
+ if self.do_advance:
+ self.do_advance = False
+ if grouper_iter is not None:
+ try:
+ next(grouper_iter)
+ except StopIteration:
+ pass
+ return NotImplemented
+ return True
+
+ def keyfunc(element):
+ if element == 0:
+ return Key(do_advance=True)
+ return Key(do_advance=False)
+
+ g = itertools.groupby(range(4), keyfunc)
+ key, grouper_iter = next(g)
+ items = list(grouper_iter)
+ self.assertEqual(len(items), 1)
+
def test_filter(self):
self.assertEqual(list(filter(isEven, range(6))), [0,2,4])
self.assertEqual(list(filter(None, [0,1,0,2,0])), [1,2])
diff --git
a/Misc/NEWS.d/next/Library/2026-04-01-11-05-36.gh-issue-146613.GzjUFK.rst
b/Misc/NEWS.d/next/Library/2026-04-01-11-05-36.gh-issue-146613.GzjUFK.rst
new file mode 100644
index 00000000000000..94e198e7b28ad8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-01-11-05-36.gh-issue-146613.GzjUFK.rst
@@ -0,0 +1,2 @@
+:mod:`itertools`: Fix a crash in :func:`itertools.groupby` when
+the grouper iterator is concurrently mutated.
diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c
index cf49724b8861c2..0453a166c843ad 100644
--- a/Modules/itertoolsmodule.c
+++ b/Modules/itertoolsmodule.c
@@ -678,7 +678,16 @@ _grouper_next(PyObject *op)
}
assert(gbo->currkey != NULL);
- rcmp = PyObject_RichCompareBool(igo->tgtkey, gbo->currkey, Py_EQ);
+ /* A user-defined __eq__ can re-enter the grouper and advance the iterator,
+ mutating gbo->currkey while we are comparing them.
+ Take local snapshots and hold strong references so INCREF/DECREF
+ apply to the same objects even under re-entrancy. */
+ PyObject *tgtkey = Py_NewRef(igo->tgtkey);
+ PyObject *currkey = Py_NewRef(gbo->currkey);
+ rcmp = PyObject_RichCompareBool(tgtkey, currkey, Py_EQ);
+ Py_DECREF(tgtkey);
+ Py_DECREF(currkey);
+
if (rcmp <= 0)
/* got any error or current group is end */
return NULL;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]