Skip to content

Commit e8c3d31

Browse files
[3.14] gh-146059: Call fast_save_leave() in pickle save_frozenset() (GH-146173) (#146473)
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 25714b3 commit e8c3d31

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

Lib/test/pickletester.py

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

60+
FAST_NESTING_LIMIT = 50
61+
6062

6163
# Return True if opcode code appears in the pickle, else False.
6264
def opcode_in_pickle(code, pickle):
@@ -4273,6 +4275,98 @@ def __reduce__(self):
42734275
expected = "changed size during iteration"
42744276
self.assertIn(expected, str(e))
42754277

4278+
def fast_save_enter(self, create_data, minprotocol=0):
4279+
# gh-146059: Check that fast_save() is called when
4280+
# fast_save_enter() is called.
4281+
if not hasattr(self, "pickler"):
4282+
self.skipTest("need Pickler class")
4283+
4284+
data = [create_data(i) for i in range(FAST_NESTING_LIMIT * 2)]
4285+
data = {"key": data}
4286+
protocols = range(minprotocol, pickle.HIGHEST_PROTOCOL + 1)
4287+
for proto in protocols:
4288+
with self.subTest(proto=proto):
4289+
buf = io.BytesIO()
4290+
pickler = self.pickler(buf, protocol=proto)
4291+
# Enable fast mode (disables memo, enables cycle detection)
4292+
pickler.fast = 1
4293+
pickler.dump(data)
4294+
4295+
buf.seek(0)
4296+
data2 = self.unpickler(buf).load()
4297+
self.assertEqual(data2, data)
4298+
4299+
def test_fast_save_enter_tuple(self):
4300+
self.fast_save_enter(lambda i: (i,))
4301+
4302+
def test_fast_save_enter_list(self):
4303+
self.fast_save_enter(lambda i: [i])
4304+
4305+
def test_fast_save_enter_frozenset(self):
4306+
self.fast_save_enter(lambda i: frozenset([i]))
4307+
4308+
def test_fast_save_enter_set(self):
4309+
self.fast_save_enter(lambda i: set([i]))
4310+
4311+
def test_fast_save_enter_frozendict(self):
4312+
if self.py_version < (3, 15):
4313+
self.skipTest('need frozendict')
4314+
self.fast_save_enter(lambda i: frozendict(key=i), minprotocol=2)
4315+
4316+
def test_fast_save_enter_dict(self):
4317+
self.fast_save_enter(lambda i: {"key": i})
4318+
4319+
def deep_nested_struct(self, seed, create_nested,
4320+
minprotocol=0, compare_equal=True,
4321+
depth=FAST_NESTING_LIMIT * 2):
4322+
# gh-146059: Check that fast_save() is called when
4323+
# fast_save_enter() is called.
4324+
if not hasattr(self, "pickler"):
4325+
self.skipTest("need Pickler class")
4326+
4327+
data = seed
4328+
for i in range(depth):
4329+
data = create_nested(data)
4330+
data = {"key": data}
4331+
protocols = range(minprotocol, pickle.HIGHEST_PROTOCOL + 1)
4332+
for proto in protocols:
4333+
with self.subTest(proto=proto):
4334+
buf = io.BytesIO()
4335+
pickler = self.pickler(buf, protocol=proto)
4336+
# Enable fast mode (disables memo, enables cycle detection)
4337+
pickler.fast = 1
4338+
pickler.dump(data)
4339+
4340+
buf.seek(0)
4341+
data2 = self.unpickler(buf).load()
4342+
if compare_equal:
4343+
self.assertEqual(data2, data)
4344+
4345+
def test_deep_nested_struct_tuple(self):
4346+
self.deep_nested_struct((1,), lambda data: (data,))
4347+
4348+
def test_deep_nested_struct_list(self):
4349+
self.deep_nested_struct([1], lambda data: [data])
4350+
4351+
def test_deep_nested_struct_frozenset(self):
4352+
self.deep_nested_struct(frozenset((1,)),
4353+
lambda data: frozenset((1, data)))
4354+
4355+
def test_deep_nested_struct_set(self):
4356+
self.deep_nested_struct({1}, lambda data: {K(data)},
4357+
depth=FAST_NESTING_LIMIT+1,
4358+
compare_equal=False)
4359+
4360+
def test_deep_nested_struct_frozendict(self):
4361+
if self.py_version < (3, 15):
4362+
self.skipTest('need frozendict')
4363+
self.deep_nested_struct(frozendict(x=1),
4364+
lambda data: frozendict(x=data),
4365+
minprotocol=2)
4366+
4367+
def test_deep_nested_struct_dict(self):
4368+
self.deep_nested_struct({'x': 1}, lambda data: {'x': data})
4369+
42764370

42774371
class BigmemPickleTests:
42784372

Modules/_pickle.c

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

35713571
static int
3572-
save_frozenset(PickleState *state, PicklerObject *self, PyObject *obj)
3572+
save_frozenset_impl(PickleState *state, PicklerObject *self, PyObject *obj)
35733573
{
35743574
PyObject *iter;
35753575

35763576
const char mark_op = MARK;
35773577
const char frozenset_op = FROZENSET;
35783578

3579-
if (self->fast && !fast_save_enter(self, obj))
3580-
return -1;
3581-
35823579
if (self->proto < 4) {
35833580
PyObject *items;
35843581
PyObject *reduce_value;
@@ -3649,6 +3646,19 @@ save_frozenset(PickleState *state, PicklerObject *self, PyObject *obj)
36493646
return 0;
36503647
}
36513648

3649+
static int
3650+
save_frozenset(PickleState *state, PicklerObject *self, PyObject *obj)
3651+
{
3652+
if (self->fast && !fast_save_enter(self, obj)) {
3653+
return -1;
3654+
}
3655+
int status = save_frozenset_impl(state, self, obj);
3656+
if (self->fast && !fast_save_leave(self, obj)) {
3657+
return -1;
3658+
}
3659+
return status;
3660+
}
3661+
36523662
static int
36533663
fix_imports(PickleState *st, PyObject **module_name, PyObject **global_name)
36543664
{

0 commit comments

Comments
 (0)