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)
This commit is contained in:
Žiga Seilnacht 2007-03-15 11:47:59 +00:00
parent 7cd6ef0913
commit c1b4e8e6e2
3 changed files with 82 additions and 14 deletions

View 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):

View File

@ -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.

View 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}
};