Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Lib/test/test_itertools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`itertools`: Fix a crash in :func:`itertools.groupby` when
the grouper iterator is concurrently mutated.
11 changes: 10 additions & 1 deletion Modules/itertoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading