summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/pickle.py10
-rw-r--r--Lib/test/pickletester.py58
-rw-r--r--Misc/NEWS3
3 files changed, 65 insertions, 6 deletions
diff --git a/Lib/pickle.py b/Lib/pickle.py
index d121ec9..b3b775f 100644
--- a/Lib/pickle.py
+++ b/Lib/pickle.py
@@ -1156,8 +1156,14 @@ class _Unpickler:
def load_appends(self):
stack = self.stack
mark = self.marker()
- list = stack[mark - 1]
- list.extend(stack[mark + 1:])
+ list_obj = stack[mark - 1]
+ items = stack[mark + 1:]
+ if isinstance(list_obj, list):
+ list_obj.extend(items)
+ else:
+ append = list_obj.append
+ for item in items:
+ append(item)
del stack[mark:]
dispatch[APPENDS[0]] = load_appends
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py
index c58b876..7e6e758 100644
--- a/Lib/test/pickletester.py
+++ b/Lib/test/pickletester.py
@@ -1219,6 +1219,29 @@ class AbstractPickleTests(unittest.TestCase):
for p, expected in goodpickles:
self.assertEqual(self.loads(p), expected)
+ def _check_pickling_with_opcode(self, obj, opcode, proto):
+ pickled = self.dumps(obj, proto)
+ self.assertTrue(opcode_in_pickle(opcode, pickled))
+ unpickled = self.loads(pickled)
+ self.assertEqual(obj, unpickled)
+
+ def test_appends_on_non_lists(self):
+ # Issue #17720
+ obj = REX_six([1, 2, 3])
+ for proto in protocols:
+ if proto == 0:
+ self._check_pickling_with_opcode(obj, pickle.APPEND, proto)
+ else:
+ self._check_pickling_with_opcode(obj, pickle.APPENDS, proto)
+
+ def test_setitems_on_non_dicts(self):
+ obj = REX_seven({1: -1, 2: -2, 3: -3})
+ for proto in protocols:
+ if proto == 0:
+ self._check_pickling_with_opcode(obj, pickle.SETITEM, proto)
+ else:
+ self._check_pickling_with_opcode(obj, pickle.SETITEMS, proto)
+
class BigmemPickleTests(unittest.TestCase):
@@ -1304,18 +1327,18 @@ class BigmemPickleTests(unittest.TestCase):
# Test classes for reduce_ex
class REX_one(object):
+ """No __reduce_ex__ here, but inheriting it from object"""
_reduce_called = 0
def __reduce__(self):
self._reduce_called = 1
return REX_one, ()
- # No __reduce_ex__ here, but inheriting it from object
class REX_two(object):
+ """No __reduce__ here, but inheriting it from object"""
_proto = None
def __reduce_ex__(self, proto):
self._proto = proto
return REX_two, ()
- # No __reduce__ here, but inheriting it from object
class REX_three(object):
_proto = None
@@ -1326,18 +1349,45 @@ class REX_three(object):
raise TestFailed("This __reduce__ shouldn't be called")
class REX_four(object):
+ """Calling base class method should succeed"""
_proto = None
def __reduce_ex__(self, proto):
self._proto = proto
return object.__reduce_ex__(self, proto)
- # Calling base class method should succeed
class REX_five(object):
+ """This one used to fail with infinite recursion"""
_reduce_called = 0
def __reduce__(self):
self._reduce_called = 1
return object.__reduce__(self)
- # This one used to fail with infinite recursion
+
+class REX_six(object):
+ """This class is used to check the 4th argument (list iterator) of the reduce
+ protocol.
+ """
+ def __init__(self, items=None):
+ self.items = items if items is not None else []
+ def __eq__(self, other):
+ return type(self) is type(other) and self.items == self.items
+ def append(self, item):
+ self.items.append(item)
+ def __reduce__(self):
+ return type(self), (), None, iter(self.items), None
+
+class REX_seven(object):
+ """This class is used to check the 5th argument (dict iterator) of the reduce
+ protocol.
+ """
+ def __init__(self, table=None):
+ self.table = table if table is not None else {}
+ def __eq__(self, other):
+ return type(self) is type(other) and self.table == self.table
+ def __setitem__(self, key, value):
+ self.table[key] = value
+ def __reduce__(self):
+ return type(self), (), None, None, iter(self.table.items())
+
# Test classes for newobj
diff --git a/Misc/NEWS b/Misc/NEWS
index a65c92c..64429ce 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -64,6 +64,9 @@ Library
- Issue #17707: multiprocessing.Queue's get() method does not block for short
timeouts.
+- Isuse #17720: Fix the Python implementation of pickle.Unpickler to correctly
+ process the APPENDS opcode when it is used on non-list objects.
+
- Issue #17012: shutil.which() no longer fallbacks to the PATH environment
variable if empty path argument is specified. Patch by Serhiy Storchaka.