* drop the unreasonable list invariant that ob_item should never come back

to NULL during the lifetime of the object.

* listobject.c nevertheless did not conform to the other invariants,
  either; fixed.

* listobject.c now uses list_clear() as the obvious internal way to clear
  a list, instead of abusing list_ass_slice() for that.  It makes it easier
  to enforce the invariant about ob_item == NULL.

* listsort() sets allocated to -1 during sort; any mutation will set it
  to a value >= 0, so it is a safe way to detect mutation.  A negative
  value for allocated does not cause a problem elsewhere currently.
  test_sort.py has a new test for this fix.

* listsort() leak: if items were added to the list during the sort, AND if
  these items had a __del__ that puts still more stuff into the list,
  then this more stuff (and the PyObject** array to hold them) were
  overridden at the end of listsort() and never released.
This commit is contained in:
Armin Rigo 2004-07-29 12:40:23 +00:00
parent f414fc4004
commit 93677f075d
3 changed files with 64 additions and 31 deletions

View File

@ -30,9 +30,7 @@ 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 * list.sort() temporarily sets allocated to -1 to detect mutations.
* 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

@ -150,6 +150,23 @@ class TestBugs(unittest.TestCase):
L.sort(None) L.sort(None)
self.assertEqual(L, range(50)) self.assertEqual(L, range(50))
def test_undetected_mutation(self):
# Python 2.4a1 did not always detect mutation
memorywaster = []
for i in range(20):
def mutating_cmp(x, y):
L.append(3)
L.pop()
return cmp(x, y)
L = [1,2]
self.assertRaises(ValueError, L.sort, mutating_cmp)
def mutating_cmp(x, y):
L.append(3)
del L[:]
return cmp(x, y)
self.assertRaises(ValueError, L.sort, mutating_cmp)
memorywaster = [memorywaster]
#============================================================================== #==============================================================================
class TestDecorateSortUndecorate(unittest.TestCase): class TestDecorateSortUndecorate(unittest.TestCase):

View File

@ -485,6 +485,29 @@ list_repeat(PyListObject *a, int n)
return (PyObject *) np; return (PyObject *) np;
} }
static int
list_clear(PyListObject *a)
{
int i;
PyObject **item = a->ob_item;
if (item != NULL) {
/* Because XDECREF can recursively invoke operations on
this list, we make it empty first. */
i = a->ob_size;
a->ob_size = 0;
a->ob_item = NULL;
a->allocated = 0;
while (--i >= 0) {
Py_XDECREF(item[i]);
}
PyMem_FREE(item);
}
/* Never fails; the return value can be ignored.
Note that there is no guarantee that the list is actually empty
at this point, because XDECREF may have populated it again! */
return 0;
}
static int static int
list_ass_slice(PyListObject *a, int ilow, int ihigh, PyObject *v) list_ass_slice(PyListObject *a, int ilow, int ihigh, PyObject *v)
{ {
@ -530,8 +553,13 @@ list_ass_slice(PyListObject *a, int ilow, int ihigh, PyObject *v)
ihigh = ilow; ihigh = ilow;
else if (ihigh > a->ob_size) else if (ihigh > a->ob_size)
ihigh = a->ob_size; ihigh = a->ob_size;
item = a->ob_item;
d = n - (ihigh-ilow); d = n - (ihigh-ilow);
if (a->ob_size + d == 0) {
Py_XDECREF(v_as_SF);
return list_clear(a);
}
item = a->ob_item;
if (ihigh > ilow) { if (ihigh > ilow) {
p = recycle = PyMem_NEW(PyObject *, (ihigh-ilow)); p = recycle = PyMem_NEW(PyObject *, (ihigh-ilow));
if (recycle == NULL) { if (recycle == NULL) {
@ -576,10 +604,6 @@ list_ass_slice(PyListObject *a, int ilow, int ihigh, PyObject *v)
Py_XDECREF(*p); Py_XDECREF(*p);
PyMem_DEL(recycle); PyMem_DEL(recycle);
} }
if (a->ob_size == 0 && a->ob_item != NULL) {
PyMem_FREE(a->ob_item);
a->ob_item = NULL;
}
Py_XDECREF(v_as_SF); Py_XDECREF(v_as_SF);
return 0; return 0;
#undef b #undef b
@ -609,12 +633,7 @@ list_inplace_repeat(PyListObject *self, int n)
} }
if (n < 1) { if (n < 1) {
items = self->ob_item; (void)list_clear(self);
self->ob_item = NULL;
self->ob_size = 0;
for (i = 0; i < size; i++)
Py_XDECREF(items[i]);
PyMem_DEL(items);
Py_INCREF(self); Py_INCREF(self);
return (PyObject *)self; return (PyObject *)self;
} }
@ -1908,6 +1927,7 @@ 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 **final_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;
@ -1942,8 +1962,9 @@ 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 = self->allocated = 0; self->ob_size = 0;
self->ob_item = NULL; self->ob_item = NULL;
self->allocated = -1; /* any operation will reset it to >= 0 */
if (keyfunc != NULL) { if (keyfunc != NULL) {
for (i=0 ; i < saved_ob_size ; i++) { for (i=0 ; i < saved_ob_size ; i++) {
@ -2032,7 +2053,7 @@ fail:
} }
} }
if (self->ob_item != NULL && result != NULL) { if (self->allocated != -1 && result != NULL) {
/* The user mucked with the list during the sort, /* The user mucked with the list during the sort,
* and we don't already have another error to report. * and we don't already have another error to report.
*/ */
@ -2046,13 +2067,19 @@ fail:
merge_freemem(&ms); merge_freemem(&ms);
dsu_fail: dsu_fail:
if (self->ob_item != NULL) { final_ob_item = self->ob_item;
(void)list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL); i = self->ob_size;
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;
if (final_ob_item != NULL) {
/* we cannot use list_clear() for this because it does not
guarantee that the list is really empty when it returns */
while (--i >= 0) {
Py_XDECREF(final_ob_item[i]);
}
PyMem_FREE(final_ob_item);
}
Py_XDECREF(compare); Py_XDECREF(compare);
Py_XINCREF(result); Py_XINCREF(result);
return result; return result;
@ -2207,13 +2234,6 @@ list_traverse(PyListObject *o, visitproc visit, void *arg)
return 0; return 0;
} }
static int
list_clear(PyListObject *lp)
{
(void) PyList_SetSlice((PyObject *)lp, 0, lp->ob_size, 0);
return 0;
}
static PyObject * static PyObject *
list_richcompare(PyObject *v, PyObject *w, int op) list_richcompare(PyObject *v, PyObject *w, int op)
{ {
@ -2295,10 +2315,8 @@ list_init(PyListObject *self, PyObject *args, PyObject *kw)
if (!PyArg_ParseTupleAndKeywords(args, kw, "|O:list", kwlist, &arg)) if (!PyArg_ParseTupleAndKeywords(args, kw, "|O:list", kwlist, &arg))
return -1; return -1;
/* Empty previous contents */ /* Empty previous contents */
self->allocated = self->ob_size; if (self->ob_item != NULL) {
if (self->ob_size != 0) { (void)list_clear(self);
if (list_ass_slice(self, 0, self->ob_size, (PyObject *)NULL) != 0)
return -1;
} }
if (arg != NULL) { if (arg != NULL) {
PyObject *rv = listextend(self, arg); PyObject *rv = listextend(self, arg);