|
| 1 | +import builtins |
1 | 2 | import collections
|
2 | 3 | import copyreg
|
3 | 4 | import dbm
|
|
11 | 12 | import struct
|
12 | 13 | import sys
|
13 | 14 | import threading
|
| 15 | +import types |
14 | 16 | import unittest
|
15 | 17 | import weakref
|
16 | 18 | from textwrap import dedent
|
@@ -1380,6 +1382,7 @@ def test_truncated_data(self):
|
1380 | 1382 | self.check_unpickling_error(self.truncated_errors, p)
|
1381 | 1383 |
|
1382 | 1384 | @threading_helper.reap_threads
|
| 1385 | + @threading_helper.requires_working_threading() |
1383 | 1386 | def test_unpickle_module_race(self):
|
1384 | 1387 | # https://bugs.python.org/issue34572
|
1385 | 1388 | locker_module = dedent("""
|
@@ -1822,6 +1825,14 @@ def test_unicode_high_plane(self):
|
1822 | 1825 | t2 = self.loads(p)
|
1823 | 1826 | self.assert_is_copy(t, t2)
|
1824 | 1827 |
|
| 1828 | + def test_unicode_memoization(self): |
| 1829 | + # Repeated str is re-used (even when escapes added). |
| 1830 | + for proto in protocols: |
| 1831 | + for s in '', 'xyz', 'xyz\n', 'x\\yz', 'x\xa1yz\r': |
| 1832 | + p = self.dumps((s, s), proto) |
| 1833 | + s1, s2 = self.loads(p) |
| 1834 | + self.assertIs(s1, s2) |
| 1835 | + |
1825 | 1836 | def test_bytes(self):
|
1826 | 1837 | for proto in protocols:
|
1827 | 1838 | for s in b'', b'xyz', b'xyz'*100:
|
@@ -1853,6 +1864,14 @@ def test_bytearray(self):
|
1853 | 1864 | self.assertNotIn(b'bytearray', p)
|
1854 | 1865 | self.assertTrue(opcode_in_pickle(pickle.BYTEARRAY8, p))
|
1855 | 1866 |
|
| 1867 | + def test_bytearray_memoization_bug(self): |
| 1868 | + for proto in protocols: |
| 1869 | + for s in b'', b'xyz', b'xyz'*100: |
| 1870 | + b = bytearray(s) |
| 1871 | + p = self.dumps((b, b), proto) |
| 1872 | + b1, b2 = self.loads(p) |
| 1873 | + self.assertIs(b1, b2) |
| 1874 | + |
1856 | 1875 | def test_ints(self):
|
1857 | 1876 | for proto in protocols:
|
1858 | 1877 | n = sys.maxsize
|
@@ -1971,6 +1990,35 @@ def test_singleton_types(self):
|
1971 | 1990 | u = self.loads(s)
|
1972 | 1991 | self.assertIs(type(singleton), u)
|
1973 | 1992 |
|
| 1993 | + def test_builtin_types(self): |
| 1994 | + for t in builtins.__dict__.values(): |
| 1995 | + if isinstance(t, type) and not issubclass(t, BaseException): |
| 1996 | + for proto in protocols: |
| 1997 | + s = self.dumps(t, proto) |
| 1998 | + self.assertIs(self.loads(s), t) |
| 1999 | + |
| 2000 | + def test_builtin_exceptions(self): |
| 2001 | + for t in builtins.__dict__.values(): |
| 2002 | + if isinstance(t, type) and issubclass(t, BaseException): |
| 2003 | + for proto in protocols: |
| 2004 | + s = self.dumps(t, proto) |
| 2005 | + u = self.loads(s) |
| 2006 | + if proto <= 2 and issubclass(t, OSError) and t is not BlockingIOError: |
| 2007 | + self.assertIs(u, OSError) |
| 2008 | + elif proto <= 2 and issubclass(t, ImportError): |
| 2009 | + self.assertIs(u, ImportError) |
| 2010 | + else: |
| 2011 | + self.assertIs(u, t) |
| 2012 | + |
| 2013 | + # TODO: RUSTPYTHON |
| 2014 | + @unittest.expectedFailure |
| 2015 | + def test_builtin_functions(self): |
| 2016 | + for t in builtins.__dict__.values(): |
| 2017 | + if isinstance(t, types.BuiltinFunctionType): |
| 2018 | + for proto in protocols: |
| 2019 | + s = self.dumps(t, proto) |
| 2020 | + self.assertIs(self.loads(s), t) |
| 2021 | + |
1974 | 2022 | # Tests for protocol 2
|
1975 | 2023 |
|
1976 | 2024 | def test_proto(self):
|
@@ -2370,13 +2418,17 @@ def test_reduce_calls_base(self):
|
2370 | 2418 | y = self.loads(s)
|
2371 | 2419 | self.assertEqual(y._reduce_called, 1)
|
2372 | 2420 |
|
| 2421 | + # TODO: RUSTPYTHON |
| 2422 | + @unittest.expectedFailure |
2373 | 2423 | @no_tracing
|
2374 | 2424 | def test_bad_getattr(self):
|
2375 | 2425 | # Issue #3514: crash when there is an infinite loop in __getattr__
|
2376 | 2426 | x = BadGetattr()
|
2377 |
| - for proto in protocols: |
| 2427 | + for proto in range(2): |
2378 | 2428 | with support.infinite_recursion():
|
2379 | 2429 | self.assertRaises(RuntimeError, self.dumps, x, proto)
|
| 2430 | + for proto in range(2, pickle.HIGHEST_PROTOCOL + 1): |
| 2431 | + s = self.dumps(x, proto) |
2380 | 2432 |
|
2381 | 2433 | def test_reduce_bad_iterator(self):
|
2382 | 2434 | # Issue4176: crash when 4th and 5th items of __reduce__()
|
@@ -2536,6 +2588,7 @@ def check_frame_opcodes(self, pickled):
|
2536 | 2588 | self.assertLess(pos - frameless_start, self.FRAME_SIZE_MIN)
|
2537 | 2589 |
|
2538 | 2590 | @support.skip_if_pgo_task
|
| 2591 | + @support.requires_resource('cpu') |
2539 | 2592 | def test_framing_many_objects(self):
|
2540 | 2593 | obj = list(range(10**5))
|
2541 | 2594 | for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
|
@@ -3024,6 +3077,67 @@ def check_array(arr):
|
3024 | 3077 | # 2-D, non-contiguous
|
3025 | 3078 | check_array(arr[::2])
|
3026 | 3079 |
|
| 3080 | + def test_evil_class_mutating_dict(self): |
| 3081 | + # https://github.com/python/cpython/issues/92930 |
| 3082 | + from random import getrandbits |
| 3083 | + |
| 3084 | + global Bad |
| 3085 | + class Bad: |
| 3086 | + def __eq__(self, other): |
| 3087 | + return ENABLED |
| 3088 | + def __hash__(self): |
| 3089 | + return 42 |
| 3090 | + def __reduce__(self): |
| 3091 | + if getrandbits(6) == 0: |
| 3092 | + collection.clear() |
| 3093 | + return (Bad, ()) |
| 3094 | + |
| 3095 | + for proto in protocols: |
| 3096 | + for _ in range(20): |
| 3097 | + ENABLED = False |
| 3098 | + collection = {Bad(): Bad() for _ in range(20)} |
| 3099 | + for bad in collection: |
| 3100 | + bad.bad = bad |
| 3101 | + bad.collection = collection |
| 3102 | + ENABLED = True |
| 3103 | + try: |
| 3104 | + data = self.dumps(collection, proto) |
| 3105 | + self.loads(data) |
| 3106 | + except RuntimeError as e: |
| 3107 | + expected = "changed size during iteration" |
| 3108 | + self.assertIn(expected, str(e)) |
| 3109 | + |
| 3110 | + def test_evil_pickler_mutating_collection(self): |
| 3111 | + # https://github.com/python/cpython/issues/92930 |
| 3112 | + if not hasattr(self, "pickler"): |
| 3113 | + raise self.skipTest(f"{type(self)} has no associated pickler type") |
| 3114 | + |
| 3115 | + global Clearer |
| 3116 | + class Clearer: |
| 3117 | + pass |
| 3118 | + |
| 3119 | + def check(collection): |
| 3120 | + class EvilPickler(self.pickler): |
| 3121 | + def persistent_id(self, obj): |
| 3122 | + if isinstance(obj, Clearer): |
| 3123 | + collection.clear() |
| 3124 | + return None |
| 3125 | + pickler = EvilPickler(io.BytesIO(), proto) |
| 3126 | + try: |
| 3127 | + pickler.dump(collection) |
| 3128 | + except RuntimeError as e: |
| 3129 | + expected = "changed size during iteration" |
| 3130 | + self.assertIn(expected, str(e)) |
| 3131 | + |
| 3132 | + for proto in protocols: |
| 3133 | + check([Clearer()]) |
| 3134 | + check([Clearer(), Clearer()]) |
| 3135 | + check({Clearer()}) |
| 3136 | + check({Clearer(), Clearer()}) |
| 3137 | + check({Clearer(): 1}) |
| 3138 | + check({Clearer(): 1, Clearer(): 2}) |
| 3139 | + check({1: Clearer(), 2: Clearer()}) |
| 3140 | + |
3027 | 3141 |
|
3028 | 3142 | class BigmemPickleTests:
|
3029 | 3143 |
|
@@ -3363,6 +3477,84 @@ def __init__(self): pass
|
3363 | 3477 | self.assertRaises(pickle.PicklingError, BadPickler().dump, 0)
|
3364 | 3478 | self.assertRaises(pickle.UnpicklingError, BadUnpickler().load)
|
3365 | 3479 |
|
| 3480 | + def test_unpickler_bad_file(self): |
| 3481 | + # bpo-38384: Crash in _pickle if the read attribute raises an error. |
| 3482 | + def raises_oserror(self, *args, **kwargs): |
| 3483 | + raise OSError |
| 3484 | + @property |
| 3485 | + def bad_property(self): |
| 3486 | + 1/0 |
| 3487 | + |
| 3488 | + # File without read and readline |
| 3489 | + class F: |
| 3490 | + pass |
| 3491 | + self.assertRaises((AttributeError, TypeError), self.Unpickler, F()) |
| 3492 | + |
| 3493 | + # File without read |
| 3494 | + class F: |
| 3495 | + readline = raises_oserror |
| 3496 | + self.assertRaises((AttributeError, TypeError), self.Unpickler, F()) |
| 3497 | + |
| 3498 | + # File without readline |
| 3499 | + class F: |
| 3500 | + read = raises_oserror |
| 3501 | + self.assertRaises((AttributeError, TypeError), self.Unpickler, F()) |
| 3502 | + |
| 3503 | + # File with bad read |
| 3504 | + class F: |
| 3505 | + read = bad_property |
| 3506 | + readline = raises_oserror |
| 3507 | + self.assertRaises(ZeroDivisionError, self.Unpickler, F()) |
| 3508 | + |
| 3509 | + # File with bad readline |
| 3510 | + class F: |
| 3511 | + readline = bad_property |
| 3512 | + read = raises_oserror |
| 3513 | + self.assertRaises(ZeroDivisionError, self.Unpickler, F()) |
| 3514 | + |
| 3515 | + # File with bad readline, no read |
| 3516 | + class F: |
| 3517 | + readline = bad_property |
| 3518 | + self.assertRaises(ZeroDivisionError, self.Unpickler, F()) |
| 3519 | + |
| 3520 | + # File with bad read, no readline |
| 3521 | + class F: |
| 3522 | + read = bad_property |
| 3523 | + self.assertRaises((AttributeError, ZeroDivisionError), self.Unpickler, F()) |
| 3524 | + |
| 3525 | + # File with bad peek |
| 3526 | + class F: |
| 3527 | + peek = bad_property |
| 3528 | + read = raises_oserror |
| 3529 | + readline = raises_oserror |
| 3530 | + try: |
| 3531 | + self.Unpickler(F()) |
| 3532 | + except ZeroDivisionError: |
| 3533 | + pass |
| 3534 | + |
| 3535 | + # File with bad readinto |
| 3536 | + class F: |
| 3537 | + readinto = bad_property |
| 3538 | + read = raises_oserror |
| 3539 | + readline = raises_oserror |
| 3540 | + try: |
| 3541 | + self.Unpickler(F()) |
| 3542 | + except ZeroDivisionError: |
| 3543 | + pass |
| 3544 | + |
| 3545 | + def test_pickler_bad_file(self): |
| 3546 | + # File without write |
| 3547 | + class F: |
| 3548 | + pass |
| 3549 | + self.assertRaises(TypeError, self.Pickler, F()) |
| 3550 | + |
| 3551 | + # File with bad write |
| 3552 | + class F: |
| 3553 | + @property |
| 3554 | + def write(self): |
| 3555 | + 1/0 |
| 3556 | + self.assertRaises(ZeroDivisionError, self.Pickler, F()) |
| 3557 | + |
3366 | 3558 | def check_dumps_loads_oob_buffers(self, dumps, loads):
|
3367 | 3559 | # No need to do the full gamut of tests here, just enough to
|
3368 | 3560 | # check that dumps() and loads() redirect their arguments
|
|
0 commit comments