diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 642b54d34849da..fb0fc945761e05 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -245,7 +245,7 @@ def __eq__(self, other): list1 = [X()] list2 = [Y()] - self.assertTrue(list1 == list2) + self.assertFalse(list1 == list2) list3 = [Z()] list4 = [1] diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst new file mode 100644 index 00000000000000..e30a3dee6f54dd --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-13-00-00-00.gh-issue-148442.aBcDeF.rst @@ -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. diff --git a/Objects/listobject.c b/Objects/listobject.c index 4a98c8e54ab03f..926625069a886f 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3432,7 +3432,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]; @@ -3443,36 +3446,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 *