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 19 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
26 changes: 14 additions & 12 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 @@ -1610,7 +1612,7 @@ def load_binget(self):
i = self.read(1)[0]
try:
self.append(self.memo[i])
except KeyError as exc:
except KeyError:
msg = f'Memo value not found at index {i}'
raise UnpicklingError(msg) from None
dispatch[BINGET[0]] = load_binget
Expand All @@ -1619,7 +1621,7 @@ def load_long_binget(self):
i, = unpack('<I', self.read(4))
try:
self.append(self.memo[i])
except KeyError as exc:
except KeyError:
msg = f'Memo value not found at index {i}'
raise UnpicklingError(msg) from None
dispatch[LONG_BINGET[0]] = load_long_binget
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
16 changes: 16 additions & 0 deletions Lib/test/test_pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,22 @@ def test_multiprocessing_exceptions(self):
('multiprocessing.context', name))


class PickleReductionMethodsTests(unittest.TestCase):

def test_pickle_invalid_reduction_method(self):
class C:
__reduce_ex__ = None
c = C()
with self.assertRaises(TypeError):
pickle.dumps(c)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What implementation does it test?

The tests in this module is organized in special way. There are abstract classes which test with the specified implementation specified by class attributes, and there are concrete subclasses which specialize tests for Python and C implementations. It would be better to rewrite your tests in conformance with existing style.

Copy link
Contributor Author

@eendebakpt eendebakpt Aug 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@serhiy-storchaka I moved the tests to be part of AbstractPickleTests, AbstractPicklerUnpicklerObjectTests and AbstractDispatchTableTests. These are included in concrete tests both inside and outside the has_c_implementation section of test_pickle.py.


class C:
__reduce__ = None
c = C()
with self.assertRaises(TypeError):
pickle.dumps(c)


def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite())
return tests
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 and update the implementation of the copy module to match the pickle module conventions. For all modules setting reduction methods like ``__reduce_ex__`` or ``__reduce__`` to ``None`` will result in a ``TypeError`` with pickling or copying an object.
6 changes: 3 additions & 3 deletions Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -3987,7 +3987,7 @@ save_reduce(PickleState *st, PicklerObject *self, PyObject *args,
size = PyTuple_Size(args);
if (size < 2 || size > 6) {
PyErr_SetString(st->PicklingError, "tuple returned by "
"__reduce__ must contain 2 through 6 elements");
"__reduce__ must contain 2 to 6 elements");
return -1;
}

Expand Down Expand Up @@ -4417,6 +4417,7 @@ save(PickleState *st, PicklerObject *self, PyObject *obj, int pers_save)
Py_INCREF(reduce_func);
}
} else {
// check private dispatch table
reduce_func = PyObject_GetItem(self->dispatch_table,
(PyObject *)type);
if (reduce_func == NULL) {
Expand Down Expand Up @@ -4447,8 +4448,7 @@ save(PickleState *st, PicklerObject *self, PyObject *obj, int pers_save)
goto error;
}
if (reduce_func != NULL) {
PyObject *proto;
proto = PyLong_FromLong(self->proto);
PyObject *proto = PyLong_FromLong(self->proto);
if (proto != NULL) {
reduce_value = _Pickle_FastCall(reduce_func, proto);
}
Expand Down