diff options
-rw-r--r-- | Lib/test/pickletester.py | 16 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Modules/_pickle.c | 44 |
3 files changed, 47 insertions, 16 deletions
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 4c301ad..21deaff 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -876,6 +876,22 @@ class AbstractPickleTests(unittest.TestCase): d = self.dumps(x, 2) self.assertRaises(RuntimeError, self.loads, d) + 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, [] + + for proto in protocols: + self.assertRaises(pickle.PickleError, self.dumps, C(), proto) + self.assertRaises(pickle.PickleError, self.dumps, D(), proto) + # Test classes for reduce_ex class REX_one(object): @@ -15,6 +15,9 @@ What's New in Python 3.0 beta 5 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 3723: Fixed initialization of subinterpreters. - Issue #4213: The file system encoding is now normalized by the diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 2b672a7..a689c33 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -1961,8 +1961,9 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj) PyObject *callable; PyObject *argtup; PyObject *state = NULL; - PyObject *listitems = NULL; - PyObject *dictitems = NULL; + PyObject *listitems = Py_None; + PyObject *dictitems = Py_None; + Py_ssize_t size; int use_newobj = self->proto >= 2; @@ -1970,27 +1971,48 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj) const char build_op = BUILD; const char newobj_op = NEWOBJ; + size = PyTuple_Size(args); + if (size < 2 || size > 5) { + PyErr_SetString(PicklingError, "tuple returned by " + "__reduce__ must contain 2 through 5 elements"); + return -1; + } + if (!PyArg_UnpackTuple(args, "save_reduce", 2, 5, &callable, &argtup, &state, &listitems, &dictitems)) return -1; if (!PyCallable_Check(callable)) { - PyErr_SetString(PicklingError, - "first argument of save_reduce() must be callable"); + PyErr_SetString(PicklingError, "first item of the tuple " + "returned by __reduce__ must be callable"); return -1; } if (!PyTuple_Check(argtup)) { - PyErr_SetString(PicklingError, - "second argument of save_reduce() must be a tuple"); + PyErr_SetString(PicklingError, "second item of the tuple " + "returned by __reduce__ must be a tuple"); return -1; } if (state == Py_None) state = NULL; + if (listitems == Py_None) listitems = NULL; + else if (!PyIter_Check(listitems)) { + PyErr_Format(PicklingError, "Fourth element of tuple" + "returned by __reduce__ must be an iterator, not %s", + Py_TYPE(listitems)->tp_name); + return -1; + } + if (dictitems == Py_None) dictitems = NULL; + else if (!PyIter_Check(dictitems)) { + PyErr_Format(PicklingError, "Fifth element of tuple" + "returned by __reduce__ must be an iterator, not %s", + Py_TYPE(dictitems)->tp_name); + return -1; + } /* Protocol 2 special case: if callable's name is __newobj__, use NEWOBJ. */ @@ -2309,16 +2331,6 @@ save(PicklerObject *self, PyObject *obj, int pers_save) "__reduce__ must return a string or tuple"); goto error; } - if (Py_SIZE(reduce_value) < 2 || Py_SIZE(reduce_value) > 5) { - PyErr_SetString(PicklingError, "tuple returned by __reduce__ " - "must contain 2 through 5 elements"); - goto error; - } - if (!PyTuple_Check(PyTuple_GET_ITEM(reduce_value, 1))) { - PyErr_SetString(PicklingError, "second item of the tuple " - "returned by __reduce__ must be a tuple"); - goto error; - } status = save_reduce(self, reduce_value, obj); |