Skip to content

gh-149676: fix: properly initialise frozendict hash value#149675

Open
KowalskiThomas wants to merge 3 commits into
python:mainfrom
KowalskiThomas:kowalski/fix-properly-initialise-frozendicts-hash-value
Open

gh-149676: fix: properly initialise frozendict hash value#149675
KowalskiThomas wants to merge 3 commits into
python:mainfrom
KowalskiThomas:kowalski/fix-properly-initialise-frozendicts-hash-value

Conversation

@KowalskiThomas
Copy link
Copy Markdown
Contributor

@KowalskiThomas KowalskiThomas commented May 11, 2026

@KowalskiThomas KowalskiThomas changed the title fix: properly initialise frozendict hash value gh-149676: fix: properly initialise frozendict hash value May 11, 2026
@KowalskiThomas KowalskiThomas marked this pull request as ready for review May 11, 2026 13:58
Comment thread Objects/dictobject.c
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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this assignment to new_dict_impl()? Adding an int frozendict parameter.

Comment thread Objects/dictobject.c
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. */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this comment is useful.

Comment thread Lib/test/test_dict.py
with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
hash(fd)

def test_hash_pipe_operator(self):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should move this test to existing test_merge().

Comment thread Lib/test/test_dict.py
Comment on lines +1906 to +1914
# 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))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The self.assertEqual(c, c_union) test is redundant with test_merge() tests. I suggest to simplify the test to:

Suggested change
# 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))
# gh-149676: Test hash(frozendict | frozendict)
a = frozendict({"a": 1})
b = frozendict({"b": 2})
self.assertEqual(hash(a | b), hash(frozendict({"a": 1, "b": 2})))

@@ -0,0 +1 @@
Fix :class:`frozendict` instances being created with an uninitialised hash value, which could cause incorrect hash lookups or crashes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue cannot lead to a crash. I suggest:

Fix frozendict | frozendict hash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants