mirror of
https://github.com/python/cpython.git
synced 2025-10-04 22:20:46 +00:00
Issue #4176: Pickle would crash the interpreter when a __reduce__ function
does not return an iterator for the 4th and 5th items. (sequence-like and mapping-like state) Backport of r67049
This commit is contained in:
parent
68daeb0f85
commit
c353ea795c
3 changed files with 59 additions and 26 deletions
|
@ -849,6 +849,29 @@ class AbstractPickleTests(unittest.TestCase):
|
||||||
y = self.loads(s)
|
y = self.loads(s)
|
||||||
self.assertEqual(y._reduce_called, 1)
|
self.assertEqual(y._reduce_called, 1)
|
||||||
|
|
||||||
|
def test_reduce_bad_iterator(self):
|
||||||
|
# Issue4176: crash when 4th and 5th items of __reduce__()
|
||||||
|
# are not iterators
|
||||||
|
class C(object):
|
||||||
|
def __reduce__(self):
|
||||||
|
# 4th item is not an iterator
|
||||||
|
return list, (), None, [], None
|
||||||
|
class D(object):
|
||||||
|
def __reduce__(self):
|
||||||
|
# 5th item is not an iterator
|
||||||
|
return dict, (), None, None, []
|
||||||
|
|
||||||
|
# Protocol 0 is less strict and also accept iterables.
|
||||||
|
for proto in 0, 1, 2:
|
||||||
|
try:
|
||||||
|
self.dumps(C(), proto)
|
||||||
|
except (AttributeError, pickle.PickleError, cPickle.PickleError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.dumps(D(), proto)
|
||||||
|
except (AttributeError, pickle.PickleError, cPickle.PickleError):
|
||||||
|
pass
|
||||||
|
|
||||||
# Test classes for reduce_ex
|
# Test classes for reduce_ex
|
||||||
|
|
||||||
class REX_one(object):
|
class REX_one(object):
|
||||||
|
|
|
@ -12,6 +12,9 @@ What's New in Python 2.6.1 alpha 1
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #4176: Fixed a crash when pickling an object which ``__reduce__``
|
||||||
|
method does not return iterators for the 4th and 5th items.
|
||||||
|
|
||||||
- Issue #4209: Enabling unicode_literals and the print_function in the same
|
- Issue #4209: Enabling unicode_literals and the print_function in the same
|
||||||
__future__ import didn't work.
|
__future__ import didn't work.
|
||||||
|
|
||||||
|
|
|
@ -2206,13 +2206,14 @@ save_pers(Picklerobject *self, PyObject *args, PyObject *f)
|
||||||
* appropriate __reduce__ method for ob.
|
* appropriate __reduce__ method for ob.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
|
save_reduce(Picklerobject *self, PyObject *args, PyObject *fn, PyObject *ob)
|
||||||
{
|
{
|
||||||
PyObject *callable;
|
PyObject *callable;
|
||||||
PyObject *argtup;
|
PyObject *argtup;
|
||||||
PyObject *state = NULL;
|
PyObject *state = NULL;
|
||||||
PyObject *listitems = NULL;
|
PyObject *listitems = Py_None;
|
||||||
PyObject *dictitems = NULL;
|
PyObject *dictitems = Py_None;
|
||||||
|
Py_ssize_t size;
|
||||||
|
|
||||||
int use_newobj = self->proto >= 2;
|
int use_newobj = self->proto >= 2;
|
||||||
|
|
||||||
|
@ -2220,6 +2221,14 @@ save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
|
||||||
static char build = BUILD;
|
static char build = BUILD;
|
||||||
static char newobj = NEWOBJ;
|
static char newobj = NEWOBJ;
|
||||||
|
|
||||||
|
size = PyTuple_Size(args);
|
||||||
|
if (size < 2 || size > 5) {
|
||||||
|
cPickle_ErrFormat(PicklingError, "tuple returned by "
|
||||||
|
"%s must contain 2 through 5 elements",
|
||||||
|
"O", fn);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (! PyArg_UnpackTuple(args, "save_reduce", 2, 5,
|
if (! PyArg_UnpackTuple(args, "save_reduce", 2, 5,
|
||||||
&callable,
|
&callable,
|
||||||
&argtup,
|
&argtup,
|
||||||
|
@ -2229,17 +2238,32 @@ save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (!PyTuple_Check(argtup)) {
|
if (!PyTuple_Check(argtup)) {
|
||||||
PyErr_SetString(PicklingError,
|
cPickle_ErrFormat(PicklingError, "Second element of "
|
||||||
"args from reduce() should be a tuple");
|
"tuple returned by %s must be a tuple",
|
||||||
|
"O", fn);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == Py_None)
|
if (state == Py_None)
|
||||||
state = NULL;
|
state = NULL;
|
||||||
|
|
||||||
if (listitems == Py_None)
|
if (listitems == Py_None)
|
||||||
listitems = NULL;
|
listitems = NULL;
|
||||||
|
else if (!PyIter_Check(listitems)) {
|
||||||
|
cPickle_ErrFormat(PicklingError, "Fourth element of "
|
||||||
|
"tuple returned by %s must be an iterator, not %s",
|
||||||
|
"Os", fn, Py_TYPE(listitems)->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (dictitems == Py_None)
|
if (dictitems == Py_None)
|
||||||
dictitems = NULL;
|
dictitems = NULL;
|
||||||
|
else if (!PyIter_Check(dictitems)) {
|
||||||
|
cPickle_ErrFormat(PicklingError, "Fifth element of "
|
||||||
|
"tuple returned by %s must be an iterator, not %s",
|
||||||
|
"Os", fn, Py_TYPE(dictitems)->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Protocol 2 special case: if callable's name is __newobj__, use
|
/* Protocol 2 special case: if callable's name is __newobj__, use
|
||||||
* NEWOBJ. This consumes a lot of code.
|
* NEWOBJ. This consumes a lot of code.
|
||||||
|
@ -2363,9 +2387,8 @@ save(Picklerobject *self, PyObject *args, int pers_save)
|
||||||
{
|
{
|
||||||
PyTypeObject *type;
|
PyTypeObject *type;
|
||||||
PyObject *py_ob_id = 0, *__reduce__ = 0, *t = 0;
|
PyObject *py_ob_id = 0, *__reduce__ = 0, *t = 0;
|
||||||
PyObject *arg_tup;
|
|
||||||
int res = -1;
|
int res = -1;
|
||||||
int tmp, size;
|
int tmp;
|
||||||
|
|
||||||
if (Py_EnterRecursiveCall(" while pickling an object"))
|
if (Py_EnterRecursiveCall(" while pickling an object"))
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -2591,30 +2614,14 @@ save(Picklerobject *self, PyObject *args, int pers_save)
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! PyTuple_Check(t)) {
|
if (!PyTuple_Check(t)) {
|
||||||
cPickle_ErrFormat(PicklingError, "Value returned by "
|
cPickle_ErrFormat(PicklingError, "Value returned by "
|
||||||
"%s must be string or tuple",
|
"%s must be string or tuple",
|
||||||
"O", __reduce__);
|
"O", __reduce__);
|
||||||
goto finally;
|
goto finally;
|
||||||
}
|
}
|
||||||
|
|
||||||
size = PyTuple_Size(t);
|
res = save_reduce(self, t, __reduce__, args);
|
||||||
if (size < 2 || size > 5) {
|
|
||||||
cPickle_ErrFormat(PicklingError, "tuple returned by "
|
|
||||||
"%s must contain 2 through 5 elements",
|
|
||||||
"O", __reduce__);
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
|
|
||||||
arg_tup = PyTuple_GET_ITEM(t, 1);
|
|
||||||
if (!(PyTuple_Check(arg_tup) || arg_tup == Py_None)) {
|
|
||||||
cPickle_ErrFormat(PicklingError, "Second element of "
|
|
||||||
"tuple returned by %s must be a tuple",
|
|
||||||
"O", __reduce__);
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
|
|
||||||
res = save_reduce(self, t, args);
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
Py_LeaveRecursiveCall();
|
Py_LeaveRecursiveCall();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue