mirror of
https://github.com/python/cpython.git
synced 2025-11-01 18:51:43 +00:00
bpo-28416: Break reference cycles in Pickler and Unpickler subclasses (#4080)
with the persistent_id() and persistent_load() methods.
This commit is contained in:
parent
bc8ac6b00e
commit
986375ebde
3 changed files with 164 additions and 40 deletions
|
|
@ -6,6 +6,7 @@ import io
|
|||
import collections
|
||||
import struct
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import unittest
|
||||
from test import support
|
||||
|
|
@ -117,6 +118,66 @@ class PyIdPersPicklerTests(AbstractIdentityPersistentPicklerTests,
|
|||
pickler = pickle._Pickler
|
||||
unpickler = pickle._Unpickler
|
||||
|
||||
@support.cpython_only
|
||||
def test_pickler_reference_cycle(self):
|
||||
def check(Pickler):
|
||||
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
f = io.BytesIO()
|
||||
pickler = Pickler(f, proto)
|
||||
pickler.dump('abc')
|
||||
self.assertEqual(self.loads(f.getvalue()), 'abc')
|
||||
pickler = Pickler(io.BytesIO())
|
||||
self.assertEqual(pickler.persistent_id('def'), 'def')
|
||||
r = weakref.ref(pickler)
|
||||
del pickler
|
||||
self.assertIsNone(r())
|
||||
|
||||
class PersPickler(self.pickler):
|
||||
def persistent_id(subself, obj):
|
||||
return obj
|
||||
check(PersPickler)
|
||||
|
||||
class PersPickler(self.pickler):
|
||||
@classmethod
|
||||
def persistent_id(cls, obj):
|
||||
return obj
|
||||
check(PersPickler)
|
||||
|
||||
class PersPickler(self.pickler):
|
||||
@staticmethod
|
||||
def persistent_id(obj):
|
||||
return obj
|
||||
check(PersPickler)
|
||||
|
||||
@support.cpython_only
|
||||
def test_unpickler_reference_cycle(self):
|
||||
def check(Unpickler):
|
||||
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
unpickler = Unpickler(io.BytesIO(self.dumps('abc', proto)))
|
||||
self.assertEqual(unpickler.load(), 'abc')
|
||||
unpickler = Unpickler(io.BytesIO())
|
||||
self.assertEqual(unpickler.persistent_load('def'), 'def')
|
||||
r = weakref.ref(unpickler)
|
||||
del unpickler
|
||||
self.assertIsNone(r())
|
||||
|
||||
class PersUnpickler(self.unpickler):
|
||||
def persistent_load(subself, pid):
|
||||
return pid
|
||||
check(PersUnpickler)
|
||||
|
||||
class PersUnpickler(self.unpickler):
|
||||
@classmethod
|
||||
def persistent_load(cls, pid):
|
||||
return pid
|
||||
check(PersUnpickler)
|
||||
|
||||
class PersUnpickler(self.unpickler):
|
||||
@staticmethod
|
||||
def persistent_load(pid):
|
||||
return pid
|
||||
check(PersUnpickler)
|
||||
|
||||
|
||||
class PyPicklerUnpicklerObjectTests(AbstractPicklerUnpicklerObjectTests):
|
||||
|
||||
|
|
@ -197,7 +258,7 @@ if has_c_implementation:
|
|||
check_sizeof = support.check_sizeof
|
||||
|
||||
def test_pickler(self):
|
||||
basesize = support.calcobjsize('5P2n3i2n3iP')
|
||||
basesize = support.calcobjsize('6P2n3i2n3iP')
|
||||
p = _pickle.Pickler(io.BytesIO())
|
||||
self.assertEqual(object.__sizeof__(p), basesize)
|
||||
MT_size = struct.calcsize('3nP0n')
|
||||
|
|
@ -214,7 +275,7 @@ if has_c_implementation:
|
|||
0) # Write buffer is cleared after every dump().
|
||||
|
||||
def test_unpickler(self):
|
||||
basesize = support.calcobjsize('2Pn2P 2P2n2i5P 2P3n6P2n2i')
|
||||
basesize = support.calcobjsize('2P2n2P 2P2n2i5P 2P3n6P2n2i')
|
||||
unpickler = _pickle.Unpickler
|
||||
P = struct.calcsize('P') # Size of memo table entry.
|
||||
n = struct.calcsize('n') # Size of mark table entry.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue