#3640: Correct a crash in cPickle on 64bit platforms, in the case of deeply nested lists or dicts.

Reviewed by Martin von Loewis.
This commit is contained in:
Amaury Forgeot d'Arc 2008-09-11 20:56:13 +00:00
parent d2e0938362
commit 24cb382455
3 changed files with 142 additions and 67 deletions

View File

@ -75,6 +75,9 @@ C-API
Library Library
------- -------
- Issue #3640: Pickling a list or a dict uses less local variables, to reduce
stack usage in the case of deeply nested objects.
- Issue #3629: Fix sre "bytecode" validator for an end case. - Issue #3629: Fix sre "bytecode" validator for an end case.
- Issue #3811: The Unicode database was updated to 5.1. - Issue #3811: The Unicode database was updated to 5.1.

View File

@ -22,6 +22,7 @@ NB: A program that does not use __methods__ can set a higher limit.
""" """
import sys import sys
import itertools
class RecursiveBlowup1: class RecursiveBlowup1:
def __init__(self): def __init__(self):
@ -61,6 +62,23 @@ def test_getitem():
def test_recurse(): def test_recurse():
return test_recurse() return test_recurse()
def test_cpickle(_cache={}):
try:
import cPickle
except ImportError:
print "cannot import cPickle, skipped!"
return
l = None
for n in itertools.count():
try:
l = _cache[n]
continue # Already tried and it works, let's save some time
except KeyError:
for i in range(100):
l = [l]
cPickle.dumps(l, protocol=-1)
_cache[n] = l
def check_limit(n, test_func_name): def check_limit(n, test_func_name):
sys.setrecursionlimit(n) sys.setrecursionlimit(n)
if test_func_name.startswith("test_"): if test_func_name.startswith("test_"):
@ -83,5 +101,6 @@ while 1:
check_limit(limit, "test_init") check_limit(limit, "test_init")
check_limit(limit, "test_getattr") check_limit(limit, "test_getattr")
check_limit(limit, "test_getitem") check_limit(limit, "test_getitem")
check_limit(limit, "test_cpickle")
print "Limit of %d is fine" % limit print "Limit of %d is fine" % limit
limit = limit + 100 limit = limit + 100

View File

@ -1515,8 +1515,8 @@ save_tuple(Picklerobject *self, PyObject *args)
static int static int
batch_list(Picklerobject *self, PyObject *iter) batch_list(Picklerobject *self, PyObject *iter)
{ {
PyObject *obj; PyObject *obj = NULL;
PyObject *slice[BATCHSIZE]; PyObject *firstitem = NULL;
int i, n; int i, n;
static char append = APPEND; static char append = APPEND;
@ -1545,45 +1545,69 @@ batch_list(Picklerobject *self, PyObject *iter)
/* proto > 0: write in batches of BATCHSIZE. */ /* proto > 0: write in batches of BATCHSIZE. */
do { do {
/* Get next group of (no more than) BATCHSIZE elements. */ /* Get first item */
for (n = 0; n < BATCHSIZE; ++n) { firstitem = PyIter_Next(iter);
if (firstitem == NULL) {
if (PyErr_Occurred())
goto BatchFailed;
/* nothing more to add */
break;
}
/* Try to get a second item */
obj = PyIter_Next(iter);
if (obj == NULL) {
if (PyErr_Occurred())
goto BatchFailed;
/* Only one item to write */
if (save(self, firstitem, 0) < 0)
goto BatchFailed;
if (self->write_func(self, &append, 1) < 0)
goto BatchFailed;
Py_CLEAR(firstitem);
break;
}
/* More than one item to write */
/* Pump out MARK, items, APPENDS. */
if (self->write_func(self, &MARKv, 1) < 0)
goto BatchFailed;
if (save(self, firstitem, 0) < 0)
goto BatchFailed;
Py_CLEAR(firstitem);
n = 1;
/* Fetch and save up to BATCHSIZE items */
while (obj) {
if (save(self, obj, 0) < 0)
goto BatchFailed;
Py_CLEAR(obj);
n += 1;
if (n == BATCHSIZE)
break;
obj = PyIter_Next(iter); obj = PyIter_Next(iter);
if (obj == NULL) { if (obj == NULL) {
if (PyErr_Occurred()) if (PyErr_Occurred())
goto BatchFailed; goto BatchFailed;
break; break;
} }
slice[n] = obj;
} }
if (n > 1) { if (self->write_func(self, &appends, 1) < 0)
/* Pump out MARK, slice[0:n], APPENDS. */ goto BatchFailed;
if (self->write_func(self, &MARKv, 1) < 0)
goto BatchFailed;
for (i = 0; i < n; ++i) {
if (save(self, slice[i], 0) < 0)
goto BatchFailed;
}
if (self->write_func(self, &appends, 1) < 0)
goto BatchFailed;
}
else if (n == 1) {
if (save(self, slice[0], 0) < 0)
goto BatchFailed;
if (self->write_func(self, &append, 1) < 0)
goto BatchFailed;
}
for (i = 0; i < n; ++i) {
Py_DECREF(slice[i]);
}
} while (n == BATCHSIZE); } while (n == BATCHSIZE);
return 0; return 0;
BatchFailed: BatchFailed:
while (--n >= 0) { Py_XDECREF(firstitem);
Py_DECREF(slice[n]); Py_XDECREF(obj);
}
return -1; return -1;
} }
@ -1659,8 +1683,8 @@ save_list(Picklerobject *self, PyObject *args)
static int static int
batch_dict(Picklerobject *self, PyObject *iter) batch_dict(Picklerobject *self, PyObject *iter)
{ {
PyObject *p; PyObject *p = NULL;
PyObject *slice[BATCHSIZE]; PyObject *firstitem = NULL;
int i, n; int i, n;
static char setitem = SETITEM; static char setitem = SETITEM;
@ -1696,56 +1720,85 @@ batch_dict(Picklerobject *self, PyObject *iter)
/* proto > 0: write in batches of BATCHSIZE. */ /* proto > 0: write in batches of BATCHSIZE. */
do { do {
/* Get next group of (no more than) BATCHSIZE elements. */ /* Get first item */
for (n = 0; n < BATCHSIZE; ++n) { firstitem = PyIter_Next(iter);
if (firstitem == NULL) {
if (PyErr_Occurred())
goto BatchFailed;
/* nothing more to add */
break;
}
if (!PyTuple_Check(firstitem) || PyTuple_Size(firstitem) != 2) {
PyErr_SetString(PyExc_TypeError, "dict items "
"iterator must return 2-tuples");
goto BatchFailed;
}
/* Try to get a second item */
p = PyIter_Next(iter);
if (p == NULL) {
if (PyErr_Occurred())
goto BatchFailed;
/* Only one item to write */
if (save(self, PyTuple_GET_ITEM(firstitem, 0), 0) < 0)
goto BatchFailed;
if (save(self, PyTuple_GET_ITEM(firstitem, 1), 0) < 0)
goto BatchFailed;
if (self->write_func(self, &setitem, 1) < 0)
goto BatchFailed;
Py_CLEAR(firstitem);
break;
}
/* More than one item to write */
/* Pump out MARK, items, SETITEMS. */
if (self->write_func(self, &MARKv, 1) < 0)
goto BatchFailed;
if (save(self, PyTuple_GET_ITEM(firstitem, 0), 0) < 0)
goto BatchFailed;
if (save(self, PyTuple_GET_ITEM(firstitem, 1), 0) < 0)
goto BatchFailed;
Py_CLEAR(firstitem);
n = 1;
/* Fetch and save up to BATCHSIZE items */
while (p) {
if (!PyTuple_Check(p) || PyTuple_Size(p) != 2) {
PyErr_SetString(PyExc_TypeError, "dict items "
"iterator must return 2-tuples");
goto BatchFailed;
}
if (save(self, PyTuple_GET_ITEM(p, 0), 0) < 0)
goto BatchFailed;
if (save(self, PyTuple_GET_ITEM(p, 1), 0) < 0)
goto BatchFailed;
Py_CLEAR(p);
n += 1;
if (n == BATCHSIZE)
break;
p = PyIter_Next(iter); p = PyIter_Next(iter);
if (p == NULL) { if (p == NULL) {
if (PyErr_Occurred()) if (PyErr_Occurred())
goto BatchFailed; goto BatchFailed;
break; break;
} }
if (!PyTuple_Check(p) || PyTuple_Size(p) != 2) {
PyErr_SetString(PyExc_TypeError, "dict items "
"iterator must return 2-tuples");
goto BatchFailed;
}
slice[n] = p;
} }
if (n > 1) { if (self->write_func(self, &setitems, 1) < 0)
/* Pump out MARK, slice[0:n], SETITEMS. */ goto BatchFailed;
if (self->write_func(self, &MARKv, 1) < 0)
goto BatchFailed;
for (i = 0; i < n; ++i) {
p = slice[i];
if (save(self, PyTuple_GET_ITEM(p, 0), 0) < 0)
goto BatchFailed;
if (save(self, PyTuple_GET_ITEM(p, 1), 0) < 0)
goto BatchFailed;
}
if (self->write_func(self, &setitems, 1) < 0)
goto BatchFailed;
}
else if (n == 1) {
p = slice[0];
if (save(self, PyTuple_GET_ITEM(p, 0), 0) < 0)
goto BatchFailed;
if (save(self, PyTuple_GET_ITEM(p, 1), 0) < 0)
goto BatchFailed;
if (self->write_func(self, &setitem, 1) < 0)
goto BatchFailed;
}
for (i = 0; i < n; ++i) {
Py_DECREF(slice[i]);
}
} while (n == BATCHSIZE); } while (n == BATCHSIZE);
return 0; return 0;
BatchFailed: BatchFailed:
while (--n >= 0) { Py_XDECREF(firstitem);
Py_DECREF(slice[n]); Py_XDECREF(p);
}
return -1; return -1;
} }