diff options
author | Žiga Seilnacht <ziga.seilnacht@gmail.com> | 2007-03-15 11:47:59 (GMT) |
---|---|---|
committer | Žiga Seilnacht <ziga.seilnacht@gmail.com> | 2007-03-15 11:47:59 (GMT) |
commit | c1b4e8e6e2b08b14a1fe1078aa2e6efde1bf7866 (patch) | |
tree | 0a0ee3b63ced1b0822c5e2f6cbb67ced9bb1ab05 | |
parent | 7cd6ef09135f69675b54e0a8532f0063912e99ed (diff) | |
download | cpython-c1b4e8e6e2b08b14a1fe1078aa2e6efde1bf7866.zip cpython-c1b4e8e6e2b08b14a1fe1078aa2e6efde1bf7866.tar.gz cpython-c1b4e8e6e2b08b14a1fe1078aa2e6efde1bf7866.tar.bz2 |
Patch #1462488: prevent a segfault in object_reduce_ex() by splitting
the implementation for __reduce__ and __reduce_ex__ into two separate
functions. Fixes bug #931877.
(backport from rev. 54397)
-rw-r--r-- | Lib/test/pickletester.py | 32 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Objects/typeobject.c | 61 |
3 files changed, 82 insertions, 14 deletions
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index 5b9da56..e1bc078 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -831,6 +831,24 @@ class AbstractPickleTests(unittest.TestCase): y = self.loads(s) self.assertEqual(y._proto, None) + def test_reduce_ex_calls_base(self): + for proto in 0, 1, 2: + x = REX_four() + self.assertEqual(x._proto, None) + s = self.dumps(x, proto) + self.assertEqual(x._proto, proto) + y = self.loads(s) + self.assertEqual(y._proto, proto) + + def test_reduce_calls_base(self): + for proto in 0, 1, 2: + x = REX_five() + self.assertEqual(x._reduce_called, 0) + s = self.dumps(x, proto) + self.assertEqual(x._reduce_called, 1) + y = self.loads(s) + self.assertEqual(y._reduce_called, 1) + # Test classes for reduce_ex class REX_one(object): @@ -855,6 +873,20 @@ class REX_three(object): def __reduce__(self): raise TestFailed, "This __reduce__ shouldn't be called" +class REX_four(object): + _proto = None + def __reduce_ex__(self, proto): + self._proto = proto + return object.__reduce_ex__(self, proto) + # Calling base class method should succeed + +class REX_five(object): + _reduce_called = 0 + def __reduce__(self): + self._reduce_called = 1 + return object.__reduce__(self) + # This one used to fail with infinite recursion + # Test classes for newobj class MyInt(int): @@ -12,6 +12,9 @@ What's New in Python 2.5.1c1? Core and builtins ----------------- +- Patch #1462488: Python no longer segfaults when ``object.__reduce_ex__()`` + is called with an object that is faking its type. + - Patch #1680015: Don't modify __slots__ tuple if it contains an unicode name. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4a58a86..8a6f782 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2708,11 +2708,54 @@ reduce_2(PyObject *obj) return res; } +/* + * There were two problems when object.__reduce__ and object.__reduce_ex__ + * were implemented in the same function: + * - trying to pickle an object with a custom __reduce__ method that + * fell back to object.__reduce__ in certain circumstances led to + * infinite recursion at Python level and eventual RuntimeError. + * - Pickling objects that lied about their type by overwriting the + * __class__ descriptor could lead to infinite recursion at C level + * and eventual segfault. + * + * Because of backwards compatibility, the two methods still have to + * behave in the same way, even if this is not required by the pickle + * protocol. This common functionality was moved to the _common_reduce + * function. + */ +static PyObject * +_common_reduce(PyObject *self, int proto) +{ + PyObject *copy_reg, *res; + + if (proto >= 2) + return reduce_2(self); + + copy_reg = import_copy_reg(); + if (!copy_reg) + return NULL; + + res = PyEval_CallMethod(copy_reg, "_reduce_ex", "(Oi)", self, proto); + Py_DECREF(copy_reg); + + return res; +} + +static PyObject * +object_reduce(PyObject *self, PyObject *args) +{ + int proto = 0; + + if (!PyArg_ParseTuple(args, "|i:__reduce__", &proto)) + return NULL; + + return _common_reduce(self, proto); +} + static PyObject * object_reduce_ex(PyObject *self, PyObject *args) { - /* Call copy_reg._reduce_ex(self, proto) */ - PyObject *reduce, *copy_reg, *res; + PyObject *reduce, *res; int proto = 0; if (!PyArg_ParseTuple(args, "|i:__reduce_ex__", &proto)) @@ -2748,23 +2791,13 @@ object_reduce_ex(PyObject *self, PyObject *args) Py_DECREF(reduce); } - if (proto >= 2) - return reduce_2(self); - - copy_reg = import_copy_reg(); - if (!copy_reg) - return NULL; - - res = PyEval_CallMethod(copy_reg, "_reduce_ex", "(Oi)", self, proto); - Py_DECREF(copy_reg); - - return res; + return _common_reduce(self, proto); } static PyMethodDef object_methods[] = { {"__reduce_ex__", object_reduce_ex, METH_VARARGS, PyDoc_STR("helper for pickle")}, - {"__reduce__", object_reduce_ex, METH_VARARGS, + {"__reduce__", object_reduce, METH_VARARGS, PyDoc_STR("helper for pickle")}, {0} }; |