]> granicus.if.org Git - python/commitdiff
Patch #1462488: prevent a segfault in object_reduce_ex() by splitting
authorŽiga Seilnacht <ziga.seilnacht@gmail.com>
Thu, 15 Mar 2007 11:47:59 +0000 (11:47 +0000)
committerŽiga Seilnacht <ziga.seilnacht@gmail.com>
Thu, 15 Mar 2007 11:47:59 +0000 (11:47 +0000)
the implementation for __reduce__ and __reduce_ex__ into two separate
functions. Fixes bug #931877.
 (backport from rev. 54397)

Lib/test/pickletester.py
Misc/NEWS
Objects/typeobject.c

index 5b9da56d40b9e09e2b6524fd5b700a2ccc963b2e..e1bc078154190975745df2a8a8f5c7605c683316 100644 (file)
@@ -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):
index b61002619cb5c6211ad02dca44b3e016fd6d5bc3..287bac43dfa56539187a4a9067f3e37ec837f407 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.
 
index 4a58a8619eb0d1999088e6e50de00b4f410aeb0a..8a6f78266c1486f0e412488e5e4e2825b0f7e6aa 100644 (file)
@@ -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}
 };