Skip to content

Commit 51b4ade

Browse files
committed
Fix obscure breakage (relative to 2.3) in listsort: the test for list
mutation during list.sort() used to rely on that listobject.c always NULL'ed ob_item when ob_size fell to 0. That's no longer true, so the test for list mutation during a sort is no longer reliable. Changed the test to rely instead on that listobject.c now never NULLs-out ob_item after (if ever) ob_item gets a non-NULL value. This new assumption is also documented now, as a required invariant in listobject.h. The new assumption allowed some real simplification to some of the hairier code in listsort(), so is a Good Thing on that count.
1 parent 014f103 commit 51b4ade

2 files changed

Lines changed: 15 additions & 26 deletions

File tree

Include/listobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ typedef struct {
3030
* 0 <= ob_size <= allocated
3131
* len(list) == ob_size
3232
* ob_item == NULL implies ob_size == allocated == 0
33+
* If ob_item ever becomes non-NULL, it remains non-NULL for the
34+
* life of the list object. The check for mutation in list.sort()
35+
* relies on this odd detail.
3336
*/
3437
int allocated;
3538
} PyListObject;

Objects/listobject.c

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,7 +1906,6 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
19061906
int minrun;
19071907
int saved_ob_size, saved_allocated;
19081908
PyObject **saved_ob_item;
1909-
PyObject **empty_ob_item;
19101909
PyObject *compare = NULL;
19111910
PyObject *result = NULL; /* guilty until proved innocent */
19121911
int reverse = 0;
@@ -1941,9 +1940,8 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
19411940
saved_ob_size = self->ob_size;
19421941
saved_ob_item = self->ob_item;
19431942
saved_allocated = self->allocated;
1944-
self->ob_size = 0;
1945-
self->ob_item = empty_ob_item = PyMem_NEW(PyObject *, 0);
1946-
self->allocated = 0;
1943+
self->ob_size = self->allocated = 0;
1944+
self->ob_item = NULL;
19471945

19481946
if (keyfunc != NULL) {
19491947
for (i=0 ; i < saved_ob_size ; i++) {
@@ -1957,18 +1955,6 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
19571955
saved_ob_item[i] = value;
19581956
Py_DECREF(kvpair);
19591957
}
1960-
if (self->ob_item != empty_ob_item
1961-
|| self->ob_size) {
1962-
/* If the list changed *as well* we
1963-
have two errors. We let the first
1964-
one "win", but we shouldn't let
1965-
what's in the list currently
1966-
leak. */
1967-
(void)list_ass_slice(
1968-
self, 0, self->ob_size,
1969-
(PyObject *)NULL);
1970-
}
1971-
19721958
goto dsu_fail;
19731959
}
19741960
kvpair = build_sortwrapper(key, value);
@@ -2044,14 +2030,12 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
20442030
}
20452031
}
20462032

2047-
if (self->ob_item != empty_ob_item || self->ob_size) {
2048-
/* The user mucked with the list during the sort. */
2049-
(void)list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL);
2050-
if (result != NULL) {
2051-
PyErr_SetString(PyExc_ValueError,
2052-
"list modified during sort");
2053-
result = NULL;
2054-
}
2033+
if (self->ob_item != NULL && result != NULL) {
2034+
/* The user mucked with the list during the sort,
2035+
* and we don't already have another error to report.
2036+
*/
2037+
PyErr_SetString(PyExc_ValueError, "list modified during sort");
2038+
result = NULL;
20552039
}
20562040

20572041
if (reverse && saved_ob_size > 1)
@@ -2060,8 +2044,10 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
20602044
merge_freemem(&ms);
20612045

20622046
dsu_fail:
2063-
if (self->ob_item == empty_ob_item)
2064-
PyMem_FREE(empty_ob_item);
2047+
if (self->ob_item != NULL) {
2048+
(void)list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL);
2049+
PyMem_FREE(self->ob_item);
2050+
}
20652051
self->ob_size = saved_ob_size;
20662052
self->ob_item = saved_ob_item;
20672053
self->allocated = saved_allocated;

0 commit comments

Comments
 (0)