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
50 changes: 49 additions & 1 deletion Lib/test/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def __eq__(self, other):

list1 = [X()]
list2 = [Y()]
self.assertTrue(list1 == list2)
self.assertFalse(list1 == list2)

list3 = [Z()]
list4 = [1]
Expand All @@ -262,6 +262,54 @@ def __lt__(self, other):
with self.assertRaises(TypeError):
a[0] < a

def test_richcompare_stale_element_list_vitem(self):
# gh-148442: list_richcompare_impl must use the captured vitem for
# the final ordering comparison, not re-read list1's slot after __eq__
# may have mutated it.
#
# x.__eq__(0) puts AlwaysLT() into list1[0] and returns False.
class AlwaysLT:
def __eq__(self, other: object) -> bool:
return False

def __gt__(self, other: object) -> bool:
return False

class Mutating:
def __eq__(self, other: object) -> bool:
list1[0] = AlwaysLT()
return False

def __gt__(self, other: object) -> bool:
return True

list1 = [Mutating(), 0]
list2 = [0, 0]
self.assertTrue(list1 > list2)

def test_richcompare_stale_element_list_witem(self):
# gh-148442: list_richcompare_impl must use the captured witem for
# the final ordering comparison, not re-read list2's slot after __eq__
# may have mutated it.
#
# x.__eq__(0) puts AlwaysGT() into list2[0] and returns False.
class AlwaysGT:
pass

class Mutating:
def __eq__(self, other: object) -> bool:
list2[0] = AlwaysGT()
return False

def __gt__(self, other: object) -> bool:
if isinstance(other, AlwaysGT):
return False # pretend AlwaysGT beats us
return True # beat everything else (including 0)

list1 = [Mutating(), 0]
list2 = [0, 0]
self.assertTrue(list1 > list2)

def test_list_index_modifing_operand(self):
# See gh-120384
class evil:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix a race condition in ``list.__lt__``, ``list.__le__``, ``list.__gt__``,
and ``list.__ge__``. Previously, under concurrent list mutation, the items
being compared could be replaced with other objects before the final comparison,
causing the final comparison result to be incorrect.
57 changes: 30 additions & 27 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3454,7 +3454,10 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op)
Py_RETURN_TRUE;
}

/* Search for the first index where items are different */
/* Search for the first index where items are different.
* We incref vitem/witem before calling PyObject_RichCompareBool, which may
* release the GIL and allow the list to be mutated in the meantime.
*/
for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) {
PyObject *vitem = vl->ob_item[i];
PyObject *witem = wl->ob_item[i];
Expand All @@ -3465,36 +3468,36 @@ list_richcompare_impl(PyObject *v, PyObject *w, int op)
Py_INCREF(vitem);
Py_INCREF(witem);
int k = PyObject_RichCompareBool(vitem, witem, Py_EQ);
Py_DECREF(vitem);
Py_DECREF(witem);
if (k < 0)
if (k < 0) {
Py_DECREF(vitem);
Py_DECREF(witem);
return NULL;
if (!k)
break;
}

if (i >= Py_SIZE(vl) || i >= Py_SIZE(wl)) {
/* No more items to compare -- compare sizes */
Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op);
}
}
if (!k) {
/* We have a differing item -- shortcuts for EQ/NE */
if (op == Py_EQ) {
Py_DECREF(vitem);
Py_DECREF(witem);
Py_RETURN_FALSE;
}
if (op == Py_NE) {
Py_DECREF(vitem);
Py_DECREF(witem);
Py_RETURN_TRUE;
}
/* Compare the differing items using the proper operator */
PyObject *result = PyObject_RichCompare(vitem, witem, op);
Py_DECREF(vitem);
Py_DECREF(witem);
return result;
}

/* We have an item that differs -- shortcuts for EQ/NE */
if (op == Py_EQ) {
Py_RETURN_FALSE;
}
if (op == Py_NE) {
Py_RETURN_TRUE;
Py_DECREF(vitem);
Py_DECREF(witem);
}

/* Compare the final item again using the proper operator */
PyObject *vitem = vl->ob_item[i];
PyObject *witem = wl->ob_item[i];
Py_INCREF(vitem);
Py_INCREF(witem);
PyObject *result = PyObject_RichCompare(vl->ob_item[i], wl->ob_item[i], op);
Py_DECREF(vitem);
Py_DECREF(witem);
return result;
/* All compared elements were equal -- compare sizes */
Py_RETURN_RICHCOMPARE(Py_SIZE(vl), Py_SIZE(wl), op);
}

static PyObject *
Expand Down
Loading