8000 gh-50644: Forbid pickling of codecs streams (GH-109180) · python/cpython@d6892c2 · GitHub
[go: up one dir, main page]

Skip to content

Commit d6892c2

Browse files
gh-50644: Forbid pickling of codecs streams (GH-109180)
Attempts to pickle or create a shallow or deep copy of codecs streams now raise a TypeError. Previously, copying failed with a RecursionError, while pickling produced wrong results that eventually caused unpickling to fail with a RecursionError.
1 parent 71b6e26 commit d6892c2

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

Lib/codecs.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,9 @@ def __enter__(self):
414414
def __exit__(self, type, value, tb):
415415
self.stream.close()
416416

417+
def __reduce_ex__(self, proto):
418+
raise TypeError("can't serialize %s" % self.__class__.__name__)
419+
417420
###
418421

419422
class StreamReader(Codec):
@@ -663,6 +666,9 @@ def __enter__(self):
663666
def __exit__(self, type, value, tb):
664667
self.stream.close()
665668

669+
def __reduce_ex__(self, proto):
670+
raise TypeError("can't serialize %s" % self.__class__.__name__)
671+
666672
###
667673

668674
class StreamReaderWriter:
@@ -750,6 +756,9 @@ def __enter__(self):
750756
def __exit__(self, type, value, tb):
751757
self.stream.close()
752758

759+
def __reduce_ex__(self, proto):
760+
raise TypeError("can't serialize %s" % self.__class__.__name__)
761+
753762
###
754763

755764
class StreamRecoder:
@@ -866,6 +875,9 @@ def __enter__(self):
866875
def __exit__(self, type, value, tb):
867876
self.stream.close()
868877

878+
def __reduce_ex__(self, proto):
879+
raise TypeError("can't serialize %s" % self.__class__.__name__)
880+
869881
### Shortcuts
870882

871883
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):

Lib/test/test_codecs.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import codecs
22
import contextlib
3+
import copy
34
import io
45
import locale
6+
import pickle
57
import sys
68
import unittest
79
import encodings
@@ -1771,6 +1773,61 @@ def test_readlines(self):
17711773
f = self.reader(self.stream)
17721774
self.assertEqual(f.readlines(), ['\ud55c\n', '\uae00'])
17731775

1776+
def test_copy(self):
1777+
f = self.reader(Queue(b'\xed\x95\x9c\n\xea\xb8\x80'))
1778+
with self.assertRaisesRegex(TypeError, 'StreamReader'):
1779+
copy.copy(f)
1780+
with self.assertRaisesRegex(TypeError, 'StreamReader'):
1781+
copy.deepcopy(f)
1782+
1783+
def test_pickle(self):
1784+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1785+
with self.subTest(protocol=proto):
1786+
f = self.reader(Queue(b'\xed\x95\x9c\n\xea\xb8\x80'))
1787+
with self.assertRaisesRegex(TypeError, 'StreamReader'):
1788+
pickle.dumps(f, proto)
1789+
1790+
1791+
class StreamWriterTest(unittest.TestCase):
1792+
1793+
def setUp(self):
1794+
self.writer = codecs.getwriter('utf-8')
1795+
1796+
def test_copy(self):
1797+
f = self.writer(Queue(b''))
1798+
with self.assertRaisesRegex(TypeError, 'StreamWriter'):
1799+
copy.copy(f)
1800+
with self.assertRaisesRegex(TypeError, 'StreamWriter'):
1801+
copy.deepcopy(f)
1802+
1803+
def test_pickle(self):
1804+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1805+
with self.subTest(protocol=proto):
1806+
f = self.writer(Queue(b''))
1807+
with self.assertRaisesRegex(TypeError, 'StreamWriter'):
1808+
pickle.dumps(f, proto)
1809+
1810+
1811+
class StreamReaderWriterTest(unittest.TestCase):
1812+
1813+
def setUp(self):
1814+
self.reader = codecs.getreader('latin1')
1815+
self.writer = codecs.getwriter('utf-8')
1816+
1817+
def test_copy(self):
1818+
f = codecs.StreamReaderWriter(Queue(b''), self.reader, self.writer)
1819+
with self.assertRaisesRegex(TypeError, 'StreamReaderWriter'):
1820+
copy.copy(f)
1821+
with self.assertRaisesRegex(TypeError, 'StreamReaderWriter'):
1822+
copy.deepcopy(f)
1823+
1824+
def test_pickle(self):
1825+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1826+
with self.subTest(protocol=proto):
1827+
f = codecs.StreamReaderWriter(Queue(b''), self.reader, self.writer)
1828+
with self.assertRaisesRegex(TypeError, 'StreamReaderWriter'):
1829+
pickle.dumps(f, proto)
1830+
17741831

17751832
class EncodedFileTest(unittest.TestCase):
17761833

@@ -3346,6 +3403,28 @@ def test_seeking_write(self):
33463403
self.assertEqual(sr.readline(), b'abc\n')
33473404
self.assertEqual(sr.readline(), b'789\n')
33483405

3406+
def test_copy(self):
3407+
bio = io.BytesIO()
3408+
codec = codecs.lookup('ascii')
3409+
sr = codecs.StreamRecoder(bio, codec.encode, codec.decode,
3410+
encodings.ascii.StreamReader, encodings.ascii.StreamWriter)
3411+
3412+
with self.assertRaisesRegex(TypeError, 'StreamRecoder'):
3413+
copy.copy(sr)
3414+
with self.assertRaisesRegex(TypeError, 'StreamRecoder'):
3415+
copy.deepcopy(sr)
3416+
3417+
def test_pickle(self):
3418+
q = Queue(b'')
3419+
codec = codecs.lookup('ascii')
3420+
sr = codecs.StreamRecoder(q, codec.encode, codec.decode,
3421+
encodings.ascii.StreamReader, encodings.ascii.StreamWriter)
3422+
3423+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
3424+
with self.subTest(protocol=proto):
3425+
with self.assertRaisesRegex(TypeError, 'StreamRecoder'):
3426+
pickle.dumps(sr, proto)
3427+
33493428

33503429
@unittest.skipIf(_testinternalcapi is None, 'need _testinternalcapi module')
33513430
class LocaleCodecTest(unittest.TestCase):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Attempts to pickle or create a shallow or deep copy of :mod:`codecs` streams
2+
now raise a TypeError. Previously, copying failed with a RecursionError,
3+
while pickling produced wrong results that eventually caused unpickling
4+
to fail with a RecursionError.

0 commit comments

Comments
 (0)
0