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])
}
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;