Skip to content

Commit de1644c

Browse files
[3.13] gh-146059: Call fast_save_leave() in pickle save_frozenset() (GH-146173) (#146474)
gh-146059: Call fast_save_leave() in pickle save_frozenset() (GH-146173) Add more pickle tests: test also nested structures. (cherry picked from commit 5c0dcb3) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent f4f598a commit de1644c

File tree

2 files changed

+109
-4
lines changed

2 files changed

+109
-4
lines changed

Lib/test/pickletester.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
# kind of outer loop.
5959
protocols = range(pickle.HIGHEST_PROTOCOL + 1)
6060

61+
FAST_NESTING_LIMIT = 50
62+
6163

6264
# Return True if opcode code appears in the pickle, else False.
6365
def opcode_in_pickle(code, pickle):
@@ -3944,6 +3946,99 @@ def __reduce__(self):
39443946
expected = "changed size during iteration"
39453947
self.assertIn(expected, str(e))
39463948

3949+
def fast_save_enter(self, create_data, minprotocol=0):
3950+
# gh-146059: Check that fast_save() is called when
3951+
# fast_save_enter() is called.
3952+
if not hasattr(self, "pickler"):
3953+
self.skipTest("need Pickler class")
3954+
3955+
data = [create_data(i) for i in range(FAST_NESTING_LIMIT * 2)]
3956+
data = {"key": data}
3957+
protocols = range(minprotocol, pickle.HIGHEST_PROTOCOL + 1)
3958+
for proto in protocols:
3959+
with self.subTest(proto=proto):
3960+
buf = io.BytesIO()
3961+
pickler = self.pickler(buf, protocol=proto)
3962+
# Enable fast mode (disables memo, enables cycle detection)
3963+
pickler.fast = 1
3964+
pickler.dump(data)
3965+
3966+
buf.seek(0)
3967+
data2 = self.unpickler(buf).load()
3968+
self.assertEqual(data2, data)
3969+
3970+
def test_fast_save_enter_tuple(self):
3971+
self.fast_save_enter(lambda i: (i,))
3972+
3973+
def test_fast_save_enter_list(self):
3974+
self.fast_save_enter(lambda i: [i])
3975+
3976+
def test_fast_save_enter_frozenset(self):
3977+
self.fast_save_enter(lambda i: frozenset([i]))
3978+
3979+
def test_fast_save_enter_set(self):
3980+
self.fast_save_enter(lambda i: set([i]))
3981+
3982+
def test_fast_save_enter_frozendict(self):
3983+
if self.py_version < (3, 15):
3984+
self.skipTest('need frozendict')
3985+
self.fast_save_enter(lambda i: frozendict(key=i), minprotocol=2)
3986+
3987+
def test_fast_save_enter_dict(self):
3988+
self.fast_save_enter(lambda i: {"key": i})
3989+
3990+
def deep_nested_struct(self, seed, create_nested,
3991+
minprotocol=0, compare_equal=True,
3992+
depth=FAST_NESTING_LIMIT * 2):
3993+
# gh-146059: Check that fast_save() is called when
3994+
# fast_save_enter() is called.
3995+
if not hasattr(self, "pickler"):
3996+
self.skipTest("need Pickler class")
3997+
3998+
data = seed
3999+
for i in range(depth):
4000+
data = create_nested(data)
4001+
data = {"key": data}
4002+
protocols = range(minprotocol, pickle.HIGHEST_PROTOCOL + 1)
4003+
for proto in protocols:
4004+
with self.subTest(proto=proto):
4005+
buf = io.BytesIO()
4006+
pickler = self.pickler(buf, protocol=proto)
4007+
# Enable fast mode (disables memo, enables cycle detection)
4008+
pickler.fast = 1
4009+
pickler.dump(data)
4010+
4011+
buf.seek(0)
4012+
data2 = self.unpickler(buf).load()
4013+
if compare_equal:
4014+
self.assertEqual(data2, data)
4015+
4016+
def test_deep_nested_struct_tuple(self):
4017+
self.deep_nested_struct((1,), lambda data: (data,))
4018+
4019+
def test_deep_nested_struct_list(self):
4020+
self.deep_nested_struct([1], lambda data: [data])
4021+
4022+
def test_deep_nested_struct_frozenset(self):
4023+
self.deep_nested_struct(frozenset((1,)),
4024+
lambda data: frozenset((1, data)))
4025+
4026+
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
4027+
def test_deep_nested_struct_set(self):
4028+
self.deep_nested_struct({1}, lambda data: {K(data)},
4029+
depth=FAST_NESTING_LIMIT+1,
4030+
compare_equal=False)
4031+
4032+
def test_deep_nested_struct_frozendict(self):
4033+
if self.py_version < (3, 15):
4034+
self.skipTest('need frozendict')
4035+
self.deep_nested_struct(frozendict(x=1),
4036+
lambda data: frozendict(x=data),
4037+
minprotocol=2)
4038+
4039+
def test_deep_nested_struct_dict(self):
4040+
self.deep_nested_struct({'x': 1}, lambda data: {'x': data})
4041+
39474042

39484043
class BigmemPickleTests:
39494044

Modules/_pickle.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,16 +3495,13 @@ save_set(PickleState *state, PicklerObject *self, PyObject *obj)
34953495
}
34963496

34973497
static int
3498-
save_frozenset(PickleState *state, PicklerObject *self, PyObject *obj)
3498+
save_frozenset_impl(PickleState *state, PicklerObject *self, PyObject *obj)
34993499
{
35003500
PyObject *iter;
35013501

35023502
const char mark_op = MARK;
35033503
const char frozenset_op = FROZENSET;
35043504

3505-
if (self->fast && !fast_save_enter(self, obj))
3506-
return -1;
3507-
35083505
if (self->proto < 4) {
35093506
PyObject *items;
35103507
PyObject *reduce_value;
@@ -3574,6 +3571,19 @@ save_frozenset(PickleState *state, PicklerObject *self, PyObject *obj)
35743571
return 0;
35753572
}
35763573

3574+
static int
3575+
save_frozenset(PickleState *state, PicklerObject *self, PyObject *obj)
3576+
{
3577+
if (self->fast && !fast_save_enter(self, obj)) {
3578+
return -1;
3579+
}
3580+
int status = save_frozenset_impl(state, self, obj);
3581+
if (self->fast && !fast_save_leave(self, obj)) {
3582+
return -1;
3583+
}
3584+
return status;
3585+
}
3586+
35773587
static int
35783588
fix_imports(PickleState *st, PyObject **module_name, PyObject **global_name)
35793589
{

0 commit comments

Comments
 (0)