summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
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)
commitc1b4e8e6e2b08b14a1fe1078aa2e6efde1bf7866 (patch)
tree0a0ee3b63ced1b0822c5e2f6cbb67ced9bb1ab05
parent7cd6ef09135f69675b54e0a8532f0063912e99ed (diff)
downloadcpython-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.py32
-rw-r--r--Misc/NEWS3
-rw-r--r--Objects/typeobject.c61
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):
diff --git a/Misc/NEWS b/Misc/NEWS
index b610026..287bac4 100644
--- 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.
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}
};