From 41d8757524ac25f1ecbdfb631a61c165f4b6ef4f Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Fri, 7 Aug 2026 14:46:35 +0800 Subject: [PATCH] gh-155315: Fix marshal round-trip of shared frozendict references The TYPE_FROZENDICT reader reserved a reference slot but never filled it with r_ref_insert, unlike TYPE_FROZENSET, so a frozendict referenced more than once failed to load with ValueError. --- Lib/test/test_marshal.py | 10 ++++++++++ .../2026-08-07-14-45-02.gh-issue-155315.Mk9Fd2.rst | 3 +++ Python/marshal.c | 5 +++++ 3 files changed, 18 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-07-14-45-02.gh-issue-155315.Mk9Fd2.rst diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 9c4d91c456dc5d9..0e71d65f22b4f0d 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -386,6 +386,16 @@ def test_reference_loop_frozendict(self): for v in range(marshal.version + 1): self.assertRaises(ValueError, marshal.dumps, a, v) + def test_shared_reference_frozendict(self): + # A frozendict referenced more than once must round-trip with the + # shared identity preserved, like frozenset. + fd = frozendict({'a': 1, 'b': 2}) + out = marshal.loads(marshal.dumps([fd, fd])) + self.assertEqual(out[0], fd) + self.assertIs(out[0], out[1]) + nested = marshal.loads(marshal.dumps(frozendict({'x': fd, 'y': fd}))) + self.assertIs(nested['x'], nested['y']) + def test_loads_reference_loop_list(self): data = b'\xdb\x01\x00\x00\x00r\x00\x00\x00\x00' # [] a = marshal.loads(data) diff --git a/Misc/NEWS.d/next/Library/2026-08-07-14-45-02.gh-issue-155315.Mk9Fd2.rst b/Misc/NEWS.d/next/Library/2026-08-07-14-45-02.gh-issue-155315.Mk9Fd2.rst new file mode 100644 index 000000000000000..a62059c0fa26160 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-07-14-45-02.gh-issue-155315.Mk9Fd2.rst @@ -0,0 +1,3 @@ +Fix :mod:`marshal` so that a ``frozendict`` referenced more than once in the +serialized data round-trips correctly, instead of failing to load with +:exc:`ValueError`. Patch by tonghuaroot. diff --git a/Python/marshal.c b/Python/marshal.c index 25353f6e6896249..78d1c437bc24a6f 100644 --- a/Python/marshal.c +++ b/Python/marshal.c @@ -1502,6 +1502,11 @@ r_object(RFILE *p) } if (type == TYPE_FROZENDICT && v != NULL) { Py_SETREF(v, PyFrozenDict_New(v)); + /* frozendicts use delayed reference registration (like + * frozensets), so fill the slot reserved above now that the + * object exists; otherwise a later TYPE_REF to a shared + * frozendict resolves to an empty slot. */ + v = r_ref_insert(v, idx, flag, p); } retval = v; break;