summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPieter Eendebak <pieter.eendebak@gmail.com>2023-09-10 07:06:08 (GMT)
committerGitHub <noreply@github.com>2023-09-10 07:06:08 (GMT)
commit85a5d3dbe19249ba8d414d63328ae84241a35528 (patch)
treeb3e8a0510e86b1f0b8f121c698c21175bd60b47b
parent92578919a60ebe2b8d6d42377f1e27479c156d65 (diff)
downloadcpython-85a5d3dbe19249ba8d414d63328ae84241a35528.zip
cpython-85a5d3dbe19249ba8d414d63328ae84241a35528.tar.gz
cpython-85a5d3dbe19249ba8d414d63328ae84241a35528.tar.bz2
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.
-rw-r--r--Lib/pickle.py22
-rw-r--r--Lib/test/pickletester.py59
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-03-26-19-11-10.gh-issue-93627.0UgwBL.rst1
3 files changed, 72 insertions, 10 deletions
diff --git a/Lib/pickle.py b/Lib/pickle.py
index fe86f80..4f5ad5b 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -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 @@ class _Pickler:
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 @@ class _Pickler:
# 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 @@ class _Pickler:
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 @@ class _Unpickler:
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
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index a687fe0..ddb180e 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -2408,6 +2408,22 @@ class AbstractPickleTests:
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 @@ class REX_state(object):
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 @@ class AbstractPicklerUnpicklerObjectTests:
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 @@ class AbstractDispatchTableTests:
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))
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-03-26-19-11-10.gh-issue-93627.0UgwBL.rst b/Misc/NEWS.d/next/Core and Builtins/2023-03-26-19-11-10.gh-issue-93627.0UgwBL.rst
new file mode 100644
index 0000000..854da44
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-03-26-19-11-10.gh-issue-93627.0UgwBL.rst
@@ -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`.