mirror of https://github.com/python/cpython.git
gh-93627: Align Python implementation of pickle with C implementation of pickle (GH-103035)
If a method like __reduce_ex_ or __reduce__ is set to None, a TypeError is raised.
This commit is contained in:
parent
92578919a6
commit
85a5d3dbe1
|
@ -396,6 +396,8 @@ def decode_long(data):
|
|||
return int.from_bytes(data, byteorder='little', signed=True)
|
||||
|
||||
|
||||
_NoValue = object()
|
||||
|
||||
# Pickling machinery
|
||||
|
||||
class _Pickler:
|
||||
|
@ -542,8 +544,8 @@ def save(self, obj, save_persistent_id=True):
|
|||
return
|
||||
|
||||
rv = NotImplemented
|
||||
reduce = getattr(self, "reducer_override", None)
|
||||
if reduce is not None:
|
||||
reduce = getattr(self, "reducer_override", _NoValue)
|
||||
if reduce is not _NoValue:
|
||||
rv = reduce(obj)
|
||||
|
||||
if rv is NotImplemented:
|
||||
|
@ -556,8 +558,8 @@ def save(self, obj, save_persistent_id=True):
|
|||
|
||||
# Check private dispatch table if any, or else
|
||||
# copyreg.dispatch_table
|
||||
reduce = getattr(self, 'dispatch_table', dispatch_table).get(t)
|
||||
if reduce is not None:
|
||||
reduce = getattr(self, 'dispatch_table', dispatch_table).get(t, _NoValue)
|
||||
if reduce is not _NoValue:
|
||||
rv = reduce(obj)
|
||||
else:
|
||||
# Check for a class with a custom metaclass; treat as regular
|
||||
|
@ -567,12 +569,12 @@ def save(self, obj, save_persistent_id=True):
|
|||
return
|
||||
|
||||
# Check for a __reduce_ex__ method, fall back to __reduce__
|
||||
reduce = getattr(obj, "__reduce_ex__", None)
|
||||
if reduce is not None:
|
||||
reduce = getattr(obj, "__reduce_ex__", _NoValue)
|
||||
if reduce is not _NoValue:
|
||||
rv = reduce(self.proto)
|
||||
else:
|
||||
reduce = getattr(obj, "__reduce__", None)
|
||||
if reduce is not None:
|
||||
reduce = getattr(obj, "__reduce__", _NoValue)
|
||||
if reduce is not _NoValue:
|
||||
rv = reduce()
|
||||
else:
|
||||
raise PicklingError("Can't pickle %r object: %r" %
|
||||
|
@ -1705,8 +1707,8 @@ def load_build(self):
|
|||
stack = self.stack
|
||||
state = stack.pop()
|
||||
inst = stack[-1]
|
||||
setstate = getattr(inst, "__setstate__", None)
|
||||
if setstate is not None:
|
||||
setstate = getattr(inst, "__setstate__", _NoValue)
|
||||
if setstate is not _NoValue:
|
||||
setstate(state)
|
||||
return
|
||||
slotstate = None
|
||||
|
|
|
@ -2408,6 +2408,22 @@ def test_reduce_calls_base(self):
|
|||
y = self.loads(s)
|
||||
self.assertEqual(y._reduce_called, 1)
|
||||
|
||||
def test_reduce_ex_None(self):
|
||||
c = REX_None()
|
||||
with self.assertRaises(TypeError):
|
||||
self.dumps(c)
|
||||
|
||||
def test_reduce_None(self):
|
||||
c = R_None()
|
||||
with self.assertRaises(TypeError):
|
||||
self.dumps(c)
|
||||
|
||||
def test_pickle_setstate_None(self):
|
||||
c = C_None_setstate()
|
||||
p = self.dumps(c)
|
||||
with self.assertRaises(TypeError):
|
||||
self.loads(p)
|
||||
|
||||
@no_tracing
|
||||
def test_bad_getattr(self):
|
||||
# Issue #3514: crash when there is an infinite loop in __getattr__
|
||||
|
@ -3349,6 +3365,21 @@ def __setstate__(self, state):
|
|||
def __reduce__(self):
|
||||
return type(self), (), self.state
|
||||
|
||||
class REX_None:
|
||||
""" Setting __reduce_ex__ to None should fail """
|
||||
__reduce_ex__ = None
|
||||
|
||||
class R_None:
|
||||
""" Setting __reduce__ to None should fail """
|
||||
__reduce__ = None
|
||||
|
||||
class C_None_setstate:
|
||||
""" Setting __setstate__ to None should fail """
|
||||
def __getstate__(self):
|
||||
return 1
|
||||
|
||||
__setstate__ = None
|
||||
|
||||
|
||||
# Test classes for newobj
|
||||
|
||||
|
@ -3752,6 +3783,25 @@ def test_unpickling_buffering_readline(self):
|
|||
unpickler = self.unpickler_class(f)
|
||||
self.assertEqual(unpickler.load(), data)
|
||||
|
||||
def test_pickle_invalid_reducer_override(self):
|
||||
# gh-103035
|
||||
obj = object()
|
||||
|
||||
f = io.BytesIO()
|
||||
class MyPickler(self.pickler_class):
|
||||
pass
|
||||
pickler = MyPickler(f)
|
||||
pickler.dump(obj)
|
||||
|
||||
pickler.clear_memo()
|
||||
pickler.reducer_override = None
|
||||
with self.assertRaises(TypeError):
|
||||
pickler.dump(obj)
|
||||
|
||||
pickler.clear_memo()
|
||||
pickler.reducer_override = 10
|
||||
with self.assertRaises(TypeError):
|
||||
pickler.dump(obj)
|
||||
|
||||
# Tests for dispatch_table attribute
|
||||
|
||||
|
@ -3914,6 +3964,15 @@ def dumps(obj, protocol=None):
|
|||
|
||||
self._test_dispatch_table(dumps, dt)
|
||||
|
||||
def test_dispatch_table_None_item(self):
|
||||
# gh-93627
|
||||
obj = object()
|
||||
f = io.BytesIO()
|
||||
pickler = self.pickler_class(f)
|
||||
pickler.dispatch_table = {type(obj): None}
|
||||
with self.assertRaises(TypeError):
|
||||
pickler.dump(obj)
|
||||
|
||||
def _test_dispatch_table(self, dumps, dispatch_table):
|
||||
def custom_load_dump(obj):
|
||||
return pickle.loads(dumps(obj, 0))
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Update the Python pickle module implementation to match the C implementation of the pickle module. For objects setting reduction methods like :meth:`~object.__reduce_ex__` or :meth:`~object.__reduce__` to ``None``, pickling will result in a :exc:`TypeError`.
|
Loading…
Reference in New Issue