Skip to content

gh-93627: Align Python implementation of pickle with C implementation of pickle #103035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9939a1c
align python implementation of pickle, c implementation of pickle and…
eendebakpt Mar 25, 2023
29445b9
review comments
eendebakpt Mar 26, 2023
95cbf77
review comments
eendebakpt Mar 26, 2023
31d3b05
Merge branch 'main' into pickle_93627
eendebakpt Mar 26, 2023
e928112
Merge branch 'pickle_93627' of github.com:eendebakpt/cpython into pic…
eendebakpt Mar 26, 2023
fa2a741
📜🤖 Added by blurb_it.
blurb-it[bot] Mar 26, 2023
dfc4ef2
Merge branch 'pickle_93627' of github.com:eendebakpt/cpython into pic…
eendebakpt Mar 26, 2023
3e6e4eb
format news entry
eendebakpt Mar 26, 2023
92362c1
remove unused exception
eendebakpt Mar 26, 2023
bab3dab
Merge branch 'main' into pickle_93627
eendebakpt Apr 2, 2023
3a6c8c5
Merge branch 'pickle_93627' of github.com:eendebakpt/cpython into pic…
eendebakpt Apr 2, 2023
9c5cd38
align C and python pickle implementations
eendebakpt Apr 2, 2023
a8a797c
Merge branch 'main' into pickle_93627
eendebakpt Apr 4, 2023
1f1b2ff
Merge branch 'main' into pickle_93627
eendebakpt Apr 20, 2023
ec8d1c7
Merge branch 'main' into pickle_93627
eendebakpt May 5, 2023
889f2ab
Merge branch 'main' into pickle_93627
eendebakpt May 20, 2023
1900578
rename NoValue to private _NoValue
eendebakpt May 25, 2023
d822705
reverse changes to copy.py
eendebakpt May 25, 2023
c900f53
Merge branch 'main' into pickle_93627
eendebakpt May 26, 2023
a366dc5
revert style changes
eendebakpt Jun 9, 2023
2e7fbb7
Merge branch 'pickle_93627' of github.com:eendebakpt/cpython into pic…
eendebakpt Jun 9, 2023
e43d962
Merge branch 'main' into pickle_93627
eendebakpt Jun 9, 2023
1e79c83
update news entry
eendebakpt Jun 9, 2023
90a06d3
Merge branch 'pickle_93627' of github.com:eendebakpt/cpython into pic…
eendebakpt Jun 9, 2023
789fe8c
Merge branch 'main' into pickle_93627
eendebakpt Jun 12, 2023
30ffb7c
Merge branch 'main' into pickle_93627
eendebakpt Jun 26, 2023
3a88607
Merge branch 'main' into pickle_93627
eendebakpt Aug 2, 2023
dc7d33e
Merge branch 'main' into pickle_93627
eendebakpt Aug 21, 2023
d365a44
revert spelling mistake in exception message as it is part of the pub…
eendebakpt Aug 21, 2023
1bf64c0
revert adding comment to pickle c implementation
eendebakpt Aug 21, 2023
f275ede
Merge branch 'main' into pickle_93627
eendebakpt Aug 21, 2023
3548dc3
Update Misc/NEWS.d/next/Core and Builtins/2023-03-26-19-11-10.gh-issu…
eendebakpt Aug 22, 2023
250accc
add unit test for None value of __setstate__
eendebakpt Aug 22, 2023
1ea9a1b
Merge branch 'main' into pickle_93627
eendebakpt Aug 22, 2023
e58f4a1
fix tests
eendebakpt Aug 22, 2023
b1bd83c
add test for reduced_override
eendebakpt Aug 23, 2023
f565513
add test for dispatch table
eendebakpt Aug 23, 2023
4a928aa
whitespace
eendebakpt Aug 23, 2023
39ab1ed
fix test
eendebakpt Aug 24, 2023
f694005
move docstrings
eendebakpt Aug 24, 2023
833b2fb
Merge branch 'main' into pickle_93627
eendebakpt Aug 24, 2023
c803480
refactor tests
eendebakpt Aug 28, 2023
7f3ee96
fix typo
eendebakpt Aug 28, 2023
1f02ab2
whitespace
eendebakpt Aug 28, 2023
f9dd613
Merge branch 'main' into pickle_93627
eendebakpt Sep 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions Lib/pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ def decode_long(data):
return int.from_bytes(data, byteorder='little', signed=True)


_NoValue = object()

# Pickling machinery

class _Pickler:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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" %
Expand Down Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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`.