diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 61bea9dba07fec..38df89455f0ab9 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -733,6 +733,19 @@ def keyfunc(obj): keyfunc.skip = 1 self.assertRaises(ExpectedError, gulp, [None, None], keyfunc) + def test_groupby_reentrant_eq_does_not_crash(self): + class Key(bytearray): + seen = False + def __eq__(self, other): + if not Key.seen: + Key.seen = True + next(g) + return False + + g = itertools.groupby([Key(b"a"), Key(b"b")]) + next(g) + next(g) # must not segfault + 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-01-13-10-38-43.gh-issue-143543.DeQRCO.rst b/Misc/NEWS.d/next/Library/2026-01-13-10-38-43.gh-issue-143543.DeQRCO.rst new file mode 100644 index 00000000000000..14622a395ec22e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-13-10-38-43.gh-issue-143543.DeQRCO.rst @@ -0,0 +1,2 @@ +Fix a crash in itertools.groupby that could occur when a user-defined +:meth:`~object.__eq__` method re-enters the iterator during key comparison. diff --git a/Modules/itertoolsmodule.c b/Modules/itertoolsmodule.c index 8685eff8be65c3..9a064463d88f55 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -544,9 +544,21 @@ groupby_next(PyObject *op) else if (gbo->tgtkey == NULL) break; else { - int rcmp; + /* A user-defined __eq__ can re-enter groupby and advance the iterator, + mutating gbo->tgtkey / 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 = gbo->tgtkey; + PyObject *currkey = gbo->currkey; + + Py_INCREF(tgtkey); + Py_INCREF(currkey); + + int rcmp = PyObject_RichCompareBool(tgtkey, currkey, Py_EQ); + + Py_DECREF(tgtkey); + Py_DECREF(currkey); - rcmp = PyObject_RichCompareBool(gbo->tgtkey, gbo->currkey, Py_EQ); if (rcmp == -1) return NULL; else if (rcmp == 0)