Skip to content

Commit ce42c14

Browse files
serhiy-storchakamiss-islington
authored andcommitted
gh-148914: Fix memoization of in-band PickleBuffer in the Python implementation (GH-149052)
Previously, identical PickleBuffers did not preserve identity. Also, empty writable PickleBuffer memoized an empty bytearray object in place of b'' which is a singleton in CPython, so the following references to b'' were unpickled as an empty bytearray object. (cherry picked from commit b897356) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 22fdd35 commit ce42c14

3 files changed

Lines changed: 54 additions & 9 deletions

File tree

Lib/pickle.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -847,17 +847,11 @@ def save_picklebuffer(self, obj):
847847
# Write data in-band
848848
# XXX The C implementation avoids a copy here
849849
buf = m.tobytes()
850-
in_memo = id(buf) in self.memo
851850
if m.readonly:
852-
if in_memo:
853-
self._save_bytes_no_memo(buf)
854-
else:
855-
self.save_bytes(buf)
851+
self._save_bytes_no_memo(buf)
856852
else:
857-
if in_memo:
858-
self._save_bytearray_no_memo(buf)
859-
else:
860-
self.save_bytearray(buf)
853+
self._save_bytearray_no_memo(buf)
854+
self.memoize(obj)
861855
else:
862856
# Write data out-of-band
863857
self.write(NEXT_BUFFER)

Lib/test/pickletester.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,6 +2526,51 @@ def test_bytearray_memoization(self):
25262526
self.assertIsNot(b2a, b2b)
25272527
self.assert_is_copy(b2a, b2b)
25282528

2529+
def test_picklebuffer_memoization(self):
2530+
if self.py_version < (3, 8):
2531+
self.skipTest('not supported in Python < 3.8')
2532+
array_types = [bytes, bytearray]
2533+
for proto in range(5, pickle.HIGHEST_PROTOCOL + 1):
2534+
for array_type in array_types:
2535+
for s in b'', b'xyz', b'xyz'*100:
2536+
with self.subTest(proto=proto, array_type=array_type, s=s, independent=False):
2537+
b = pickle.PickleBuffer(array_type(s))
2538+
p = self.dumps((b, b), proto)
2539+
b1, b2 = self.loads(p)
2540+
self.assertIs(b1, b2)
2541+
2542+
with self.subTest(proto=proto, array_type=array_type, s=s, independent=True):
2543+
b = array_type(s)
2544+
b1a = pickle.PickleBuffer(b)
2545+
b2a = pickle.PickleBuffer(b)
2546+
p = self.dumps((b1a, b2a), proto)
2547+
b1b, b2b = self.loads(p)
2548+
if array_type is not bytes:
2549+
self.assertIsNot(b1b, b2b)
2550+
self.assert_is_copy(b1b, b)
2551+
self.assert_is_copy(b2b, b)
2552+
2553+
def test_empty_picklebuffer_memoization(self):
2554+
# gh-148914: Empty writable PickleBuffer memoized an empty bytearray
2555+
# with the id of b'' (a singleton in CPython).
2556+
if self.py_version < (3, 8):
2557+
self.skipTest('not supported in Python < 3.8')
2558+
for proto in range(5, pickle.HIGHEST_PROTOCOL + 1):
2559+
for readonly in False, True:
2560+
with self.subTest(proto=proto, readonly=readonly):
2561+
b = b''
2562+
ba = bytearray()
2563+
buf = pickle.PickleBuffer(b if readonly else ba)
2564+
p = self.dumps((buf, b, ba), proto)
2565+
buf, b, ba = self.loads(p)
2566+
array_type = bytes if readonly else bytearray
2567+
self.assertIsInstance(buf, array_type)
2568+
self.assertIsInstance(b, bytes)
2569+
self.assertIsInstance(ba, bytearray)
2570+
self.assertEqual(buf, b'')
2571+
self.assertEqual(b, b'')
2572+
self.assertEqual(ba, b'')
2573+
25292574
def test_ints(self):
25302575
for proto in protocols:
25312576
n = sys.maxsize
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix memoization of in-band :class:`~pickle.PickleBuffer` in the Python
2+
implementation of :mod:`pickle`. Previously, identical
3+
:class:`!PickleBuffer`\ s did not preserve identity, and empty writable
4+
:class:`!PickleBuffer` memoized an empty bytearray object in place of
5+
``b''``, so the following references to ``b''`` were unpickled as an empty
6+
bytearray object.

0 commit comments

Comments
 (0)