From ab9f2f4cc912e0bdd7b24a2139cd01d026e31096 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 11 May 2026 14:46:11 +0200 Subject: [PATCH 1/3] fix: properly initialise frozendict's hash value --- Objects/dictobject.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 42bc63acd9049c..fea65129dab55a 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -940,7 +940,13 @@ new_frozendict(PyDictKeysObject *keys, PyDictValues *values, Py_ssize_t used, int free_values_on_failure) { PyDictObject *mp = PyObject_GC_New(PyDictObject, &PyFrozenDict_Type); - return new_dict_impl(mp, keys, values, used, free_values_on_failure); + PyObject *result = new_dict_impl(mp, keys, values, used, free_values_on_failure); + if (result != NULL) { + /* ma_hash must be -1 (sentinel for "not computed") since PyObject_GC_New + does not zero-initialize memory and new_dict_impl does not touch ma_hash. */ + _PyFrozenDictObject_CAST(result)->ma_hash = -1; + } + return result; } static PyObject * From 0b39d23b0ff606b19fa473a72af5a2375f87b773 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 11 May 2026 14:49:30 +0200 Subject: [PATCH 2/3] misc: add news entry --- .../2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst new file mode 100644 index 00000000000000..bb5e83aa8a1b91 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst @@ -0,0 +1 @@ +Fix :class:`frozendict` instances being created with an uninitialised hash value, which could cause incorrect hash lookups or crashes. From 4ca9e476b74f025a6a48d58a21acac28d6331ae4 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 11 May 2026 16:44:42 +0200 Subject: [PATCH 3/3] test: add regression test --- Lib/test/test_dict.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index b2f4363b23e748..d37b47a5432826 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1902,6 +1902,17 @@ def test_hash(self): with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"): hash(fd) + def test_hash_pipe_operator(self): + # gh-149676: frozendict created via | must have the same hash as one + # created directly with the same contents (ma_hash must be initialised + # to -1 so that the hash is computed on first call, not left as garbage) + a = frozendict({"a": 1}) + b = frozendict({"b": 2}) + c = frozendict({"a": 1, "b": 2}) + c_union = a | b + self.assertEqual(c, c_union) + self.assertEqual(hash(c), hash(c_union)) + def test_fromkeys(self): self.assertEqual(frozendict.fromkeys('abc'), frozendict(a=None, b=None, c=None))