keyfunc.skip = 1
self.assertRaises(ExpectedError, gulp, [None, None], keyfunc)
+ def test_groupby_reentrant_eq_does_not_crash(self):
+ # regression test for gh-143543
+ class Key:
+ def __init__(self, do_advance):
+ self.do_advance = do_advance
+
+ def __eq__(self, other):
+ if self.do_advance:
+ self.do_advance = False
+ next(g)
+ return NotImplemented
+ return False
+
+ def keys():
+ yield Key(True)
+ yield Key(False)
+
+ g = itertools.groupby([None, None], keys().send)
+ next(g)
+ next(g) # must pass with address sanitizer
+
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])
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)