diff --git a/Lib/test/test_itertools.py b/Lib/test/test_itertools.py index 61bea9dba07fec..ff870bafe79aab 100644 --- a/Lib/test/test_itertools.py +++ b/Lib/test/test_itertools.py @@ -733,6 +733,22 @@ 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 + + data = [Key(b"a"), Key(b"b")] + + g = itertools.groupby(data) + 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..df04a73f374a1b 100644 --- a/Modules/itertoolsmodule.c +++ b/Modules/itertoolsmodule.c @@ -545,8 +545,19 @@ groupby_next(PyObject *op) break; else { int rcmp; + PyObject *tgtkey = gbo->tgtkey; + PyObject *currkey = gbo->currkey; + + /* Hold strong references during comparison to prevent re-entrant __eq__ + from advancing the iterator and invalidating borrowed references. */ + Py_INCREF(tgtkey); + Py_INCREF(currkey); + + 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)