Index: Modules/cPickle.c =================================================================== --- Modules/cPickle.c (revision 67001) +++ Modules/cPickle.c (working copy) @@ -2206,13 +2206,14 @@ * appropriate __reduce__ method for ob. */ static int -save_reduce(Picklerobject *self, PyObject *args, PyObject *ob) +save_reduce(Picklerobject *self, PyObject *args, PyObject *fn, PyObject *ob) { 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; @@ -2220,6 +2221,14 @@ static char build = BUILD; 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, &callable, &argtup, @@ -2229,17 +2238,32 @@ return -1; if (!PyTuple_Check(argtup)) { - PyErr_SetString(PicklingError, - "args from reduce() should be a tuple"); + cPickle_ErrFormat(PicklingError, "Second element of " + "tuple returned by %s must be a tuple", + "O", fn); return -1; } if (state == Py_None) state = NULL; + if (listitems == Py_None) 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) 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 * NEWOBJ. This consumes a lot of code. @@ -2363,9 +2387,8 @@ { PyTypeObject *type; PyObject *py_ob_id = 0, *__reduce__ = 0, *t = 0; - PyObject *arg_tup; int res = -1; - int tmp, size; + int tmp; if (Py_EnterRecursiveCall(" while pickling an object")) return -1; @@ -2591,31 +2614,15 @@ goto finally; } - if (! PyTuple_Check(t)) { + if (!PyTuple_Check(t)) { cPickle_ErrFormat(PicklingError, "Value returned by " "%s must be string or tuple", "O", __reduce__); goto finally; } - size = PyTuple_Size(t); - if (size < 2 || size> 5) { - cPickle_ErrFormat(PicklingError, "tuple returned by " - "%s must contain 2 through 5 elements", - "O", __reduce__); - goto finally; - } + res = save_reduce(self, t, __reduce__, args); - 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: Py_LeaveRecursiveCall(); Py_XDECREF(py_ob_id); Index: Lib/test/pickletester.py =================================================================== --- Lib/test/pickletester.py (revision 67001) +++ Lib/test/pickletester.py (working copy) @@ -849,6 +849,29 @@ y = self.loads(s) 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 class REX_one(object):