Skip to content

Commit 85a5d3d

Browse files
authored
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.
1 parent 9257891 commit 85a5d3d

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

Lib/pickle.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ def decode_long(data):
396396
return int.from_bytes(data, byteorder='little', signed=True)
397397

398398

399+
_NoValue = object()
400+
399401
# Pickling machinery
400402

401403
class _Pickler:
@@ -542,8 +544,8 @@ def save(self, obj, save_persistent_id=True):
542544
return
543545

544546
rv = NotImplemented
545-
reduce = getattr(self, "reducer_override", None)
546-
if reduce is not None:
547+
reduce = getattr(self, "reducer_override", _NoValue)
548+
if reduce is not _NoValue:
547549
rv = reduce(obj)
548550

549551
if rv is NotImplemented:
@@ -556,8 +558,8 @@ def save(self, obj, save_persistent_id=True):
556558

557559
# Check private dispatch table if any, or else
558560
# copyreg.dispatch_table
559-
reduce = getattr(self, 'dispatch_table', dispatch_table).get(t)
560-
if reduce is not None:
561+
reduce = getattr(self, 'dispatch_table', dispatch_table).get(t, _NoValue)
562+
if reduce is not _NoValue:
561563
rv = reduce(obj)
562564
else:
563565
# Check for a class with a custom metaclass; treat as regular
@@ -567,12 +569,12 @@ def save(self, obj, save_persistent_id=True):
567569
return
568570

569571
# Check for a __reduce_ex__ method, fall back to __reduce__
570-
reduce = getattr(obj, "__reduce_ex__", None)
571-
if reduce is not None:
572+
reduce = getattr(obj, "__reduce_ex__", _NoValue)
573+
if reduce is not _NoValue:
572574
rv = reduce(self.proto)
573575
else:
574-
reduce = getattr(obj, "__reduce__", None)
575-
if reduce is not None:
576+
reduce = getattr(obj, "__reduce__", _NoValue)
577+
if reduce is not _NoValue:
576578
rv = reduce()
577579
else:
578580
raise PicklingError("Can't pickle %r object: %r" %
@@ -1705,8 +1707,8 @@ def load_build(self):
17051707
stack = self.stack
17061708
state = stack.pop()
17071709
inst = stack[-1]
1708-
setstate = getattr(inst, "__setstate__", None)
1709-
if setstate is not None:
1710+
setstate = getattr(inst, "__setstate__", _NoValue)
1711+
if setstate is not _NoValue:
17101712
setstate(state)
17111713
return
17121714
slotstate = None

Lib/test/pickletester.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,6 +2408,22 @@ def test_reduce_calls_base(self):
24082408
y = self.loads(s)
24092409
self.assertEqual(y._reduce_called, 1)
24102410

2411+
def test_reduce_ex_None(self):
2412+
c = REX_None()
2413+
with self.assertRaises(TypeError):
2414+
self.dumps(c)
2415+
2416+
def test_reduce_None(self):
2417+
c = R_None()
2418+
with self.assertRaises(TypeError):
2419+
self.dumps(c)
2420+
2421+
def test_pickle_setstate_None(self):
2422+
c = C_None_setstate()
2423+
p = self.dumps(c)
2424+
with self.assertRaises(TypeError):
2425+
self.loads(p)
2426+
24112427
@no_tracing
24122428
def test_bad_getattr(self):
24132429
# Issue #3514: crash when there is an infinite loop in __getattr__
@@ -3349,6 +3365,21 @@ def __setstate__(self, state):
33493365
def __reduce__(self):
33503366
return type(self), (), self.state
33513367

3368+
class REX_None:
3369+
""" Setting __reduce_ex__ to None should fail """
3370+
__reduce_ex__ = None
3371+
3372+
class R_None:
3373+
""" Setting __reduce__ to None should fail """
3374+
__reduce__ = None
3375+
3376+
class C_None_setstate:
3377+
""" Setting __setstate__ to None should fail """
3378+
def __getstate__(self):
3379+
return 1
3380+
3381+
__setstate__ = None
3382+
33523383

33533384
# Test classes for newobj
33543385

@@ -3752,6 +3783,25 @@ def test_unpickling_buffering_readline(self):
37523783
unpickler = self.unpickler_class(f)
37533784
self.assertEqual(unpickler.load(), data)
37543785

3786+
def test_pickle_invalid_reducer_override(self):
3787+
# gh-103035
3788+
obj = object()
3789+
3790+
f = io.BytesIO()
3791+
class MyPickler(self.pickler_class):
3792+
pass
3793+
pickler = MyPickler(f)
3794+
pickler.dump(obj)
3795+
3796+
pickler.clear_memo()
3797+
pickler.reducer_override = None
3798+
with self.assertRaises(TypeError):
3799+
pickler.dump(obj)
3800+
3801+
pickler.clear_memo()
3802+
pickler.reducer_override = 10
3803+
with self.assertRaises(TypeError):
3804+
pickler.dump(obj)
37553805

37563806
# Tests for dispatch_table attribute
37573807

@@ -3914,6 +3964,15 @@ def dumps(obj, protocol=None):
39143964

39153965
self._test_dispatch_table(dumps, dt)
39163966

3967+
def test_dispatch_table_None_item(self):
3968+
# gh-93627
3969+
obj = object()
3970+
f = io.BytesIO()
3971+
pickler = self.pickler_class(f)
3972+
pickler.dispatch_table = {type(obj): None}
3973+
with self.assertRaises(TypeError):
3974+
pickler.dump(obj)
3975+
39173976
def _test_dispatch_table(self, dumps, dispatch_table):
39183977
def custom_load_dump(obj):
39193978
return pickle.loads(dumps(obj, 0))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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`.

0 commit comments

Comments
 (0)