You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
pickle protocol 5: writable in-band PickleBuffer with empty tobytes() can memoize b'' via BYTEARRAY8, so later bytes references unpickle as bytearray #148914
for an in-band, writable PickleBuffer, the pickler does buf = m.tobytes(). That value is always bytes
if the buffer is empty, buf is always the b''singleton, so id(buf) is the same for every such call
the pickler then does in_memo = id(buf) in self.memo
so once b'' has been memoized anywhere earlier in the same dump, every later empty in-band tobytes() in that dump sees in_memo is true
so any later part of the same pickle that is supposed to store b'' as bytes may instead reuse that memo. The unpickler then returns the bytearray, not bytes.
Code that requires bytes (strict isinstance etc.) can fail during unpickling when it receives a bytearray.
Excerpt from pickler.py, but the same applies to the C implementation:
importdillfrompickleimportPickleBuffer# a bit artificial example to trigger wrong flowdefrepro_minimal() ->None:
pb=PickleBuffer(memoryview(bytearray()))
deff():
passblob=dill.dumps((pb, f), protocol=5)
# it fails when trying to pass a bytearray instead of bytes to dill's _create_codedill.loads(blob)
print("dill.loads ok")
Both result in TypeError: code() argument 16 must be bytes, not bytearray
However, that code() case is only one example; the underlying issue is bytes vs bytearray memo reuse, and similar failures can appear anywhere bytes are required.
Sorry, I don't understand what the problem is. If m.readonly is false and in_memo is true, _save_bytearray_no_memo(buf) is called, which writes the BYTEARRAY8 opcode without writing MEMOIZE and memoizing buf. What is wrong with this?
Do you have an example that does not use third-party libraries?
I think the bug is that bytesb'' can unpickle as a bytearray if the memo for id(b'') was first created as a bytearray:
importpicklefrompickleimportPickleBufferpb=PickleBuffer(memoryview(bytearray()))
bts=b''obj= (pb, bts)
blob=pickle.dumps(obj, protocol=5)
out_pb, out_bts=pickle.loads(blob)
# it fails, because an empty out_bts is of type bytearrayassertisinstance(out_bts, bytes)
I believe treating b'' as bytes would be safer and more compatible with callers that expect empty bytes
Thank you for your example @madkrouger. I see that the problem actually is opposite to your initial description. If in_memo is false, the code for empty bytearray is written, and it is memoized as b''. Now the following references to b'' (without PickleBuffer) will be unpickled as the same empty bytearray.
There was also other bug here, for all PickleBuffers. PickleBuffers were incorrectly memoized, so identical PickleBuffers did not preserve identity after unpickling. This was only in the Python implementation.
#149052 fixes this, now the Python implementation produces the same data as the C implementation.
Activity
serhiy-storchaka commentedon Apr 26, 2026
Sorry, I don't understand what the problem is. If
m.readonlyis false andin_memois true,_save_bytearray_no_memo(buf)is called, which writes the BYTEARRAY8 opcode without writing MEMOIZE and memoizingbuf. What is wrong with this?Do you have an example that does not use third-party libraries?
madkrouger commentedon Apr 27, 2026
Hey,
I think the bug is that bytes
b''can unpickle as a bytearray if the memo forid(b'')was first created as a bytearray:I believe treating
b''as bytes would be safer and more compatible with callers that expect empty bytesserhiy-storchaka commentedon Apr 27, 2026
Thank you for your example @madkrouger. I see that the problem actually is opposite to your initial description. If
in_memois false, the code for empty bytearray is written, and it is memoized asb''. Now the following references tob''(without PickleBuffer) will be unpickled as the same empty bytearray.There was also other bug here, for all PickleBuffers. PickleBuffers were incorrectly memoized, so identical PickleBuffers did not preserve identity after unpickling. This was only in the Python implementation.
#149052 fixes this, now the Python implementation produces the same data as the C implementation.
pythongh-148914: Fix memoization of in-band PickleBuffer in the Pytho…
madkrouger commentedon Apr 28, 2026
Great, thanks for taking care of this!
gh-148914: Fix memoization of in-band PickleBuffer in the Python impl…
[3.14] gh-148914: Fix memoization of in-band PickleBuffer in the Pyth…
[3.13] gh-148914: Fix memoization of in-band PickleBuffer in the Pyth…