* 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:
parent
f414fc4004
commit
93677f075d
|
@ -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;
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue