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.
This commit is contained in:
Tim Peters 2004-07-29 04:07:15 +00:00
parent 014f103705
commit 51b4ade306
2 changed files with 15 additions and 26 deletions

View File

@ -30,6 +30,9 @@ typedef struct {
* 0 <= ob_size <= allocated * 0 <= ob_size <= allocated
* len(list) == ob_size * len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0 * ob_item == NULL implies ob_size == allocated == 0
* If ob_item ever becomes non-NULL, it remains non-NULL for the
* life of the list object. The check for mutation in list.sort()
* relies on this odd detail.
*/ */
int allocated; int allocated;
} PyListObject; } PyListObject;

View File

@ -1906,7 +1906,6 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
int minrun; int minrun;
int saved_ob_size, saved_allocated; int saved_ob_size, saved_allocated;
PyObject **saved_ob_item; PyObject **saved_ob_item;
PyObject **empty_ob_item;
PyObject *compare = NULL; PyObject *compare = NULL;
PyObject *result = NULL; /* guilty until proved innocent */ PyObject *result = NULL; /* guilty until proved innocent */
int reverse = 0; int reverse = 0;
@ -1941,9 +1940,8 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
saved_ob_size = self->ob_size; saved_ob_size = self->ob_size;
saved_ob_item = self->ob_item; saved_ob_item = self->ob_item;
saved_allocated = self->allocated; saved_allocated = self->allocated;
self->ob_size = 0; self->ob_size = self->allocated = 0;
self->ob_item = empty_ob_item = PyMem_NEW(PyObject *, 0); self->ob_item = NULL;
self->allocated = 0;
if (keyfunc != NULL) { if (keyfunc != NULL) {
for (i=0 ; i < saved_ob_size ; i++) { for (i=0 ; i < saved_ob_size ; i++) {
@ -1957,18 +1955,6 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
saved_ob_item[i] = value; saved_ob_item[i] = value;
Py_DECREF(kvpair); Py_DECREF(kvpair);
} }
if (self->ob_item != empty_ob_item
|| self->ob_size) {
/* If the list changed *as well* we
have two errors. We let the first
one "win", but we shouldn't let
what's in the list currently
leak. */
(void)list_ass_slice(
self, 0, self->ob_size,
(PyObject *)NULL);
}
goto dsu_fail; goto dsu_fail;
} }
kvpair = build_sortwrapper(key, value); kvpair = build_sortwrapper(key, value);
@ -2044,14 +2030,12 @@ fail:
} }
} }
if (self->ob_item != empty_ob_item || self->ob_size) { if (self->ob_item != NULL && result != NULL) {
/* The user mucked with the list during the sort. */ /* The user mucked with the list during the sort,
(void)list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL); * and we don't already have another error to report.
if (result != NULL) { */
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError, "list modified during sort");
"list modified during sort"); result = NULL;
result = NULL;
}
} }
if (reverse && saved_ob_size > 1) if (reverse && saved_ob_size > 1)
@ -2060,8 +2044,10 @@ fail:
merge_freemem(&ms); merge_freemem(&ms);
dsu_fail: dsu_fail:
if (self->ob_item == empty_ob_item) if (self->ob_item != NULL) {
PyMem_FREE(empty_ob_item); (void)list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL);
PyMem_FREE(self->ob_item);
}
self->ob_size = saved_ob_size; self->ob_size = saved_ob_size;
self->ob_item = saved_ob_item; self->ob_item = saved_ob_item;
self->allocated = saved_allocated; self->allocated = saved_allocated;