Skip to content

Commit 474c166

Browse files
committed
gh-149807: Fix hash(frozendict): compute (key, value) pair hash
1 parent c375292 commit 474c166

3 files changed

Lines changed: 50 additions & 9 deletions

File tree

Lib/test/test_dict.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,17 @@ def test_hash(self):
19031903
self.assertEqual(hash(frozendict(x=1, y=2)),
19041904
hash(frozendict(y=2, x=1)))
19051905

1906+
# Check that hash() computes the hash of (key, value) pairs
1907+
cases = [
1908+
frozendict(a=False, b=True, c=True),
1909+
frozendict(a=True, b=False, c=True),
1910+
frozendict(a=True, b=True, c=False),
1911+
frozendict({False: "a", "b": True, "c": True}),
1912+
frozendict({"a": "b", False: True, True: "c"}),
1913+
]
1914+
hashes = {hash(fd) for fd in cases}
1915+
self.assertEqual(len(hashes), 5)
1916+
19061917
fd = frozendict(x=[1], y=[2])
19071918
with self.assertRaisesRegex(TypeError, "unhashable type: 'list'"):
19081919
hash(fd)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``hash(frozendict)``: compute the hash of each ``(key, value)`` pair
2+
correctly. Patch by Victor Stinner.

Objects/dictobject.c

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8228,6 +8228,40 @@ _shuffle_bits(Py_uhash_t h)
82288228
return ((h ^ 89869747UL) ^ (h << 16)) * 3644798167UL;
82298229
}
82308230

8231+
// Compute hash((key, value)).
8232+
// Code copied from tuple_hash().
8233+
static Py_hash_t
8234+
frozendict_pair_hash(PyObject *key, PyObject *value)
8235+
{
8236+
Py_ssize_t len = 2;
8237+
Py_uhash_t acc = _PyTuple_HASH_XXPRIME_5;
8238+
8239+
Py_uhash_t lane = PyObject_Hash(key);
8240+
if (lane == (Py_uhash_t)-1) {
8241+
return -1;
8242+
}
8243+
acc += lane * _PyTuple_HASH_XXPRIME_2;
8244+
acc = _PyTuple_HASH_XXROTATE(acc);
8245+
acc *= _PyTuple_HASH_XXPRIME_1;
8246+
8247+
lane = PyObject_Hash(value);
8248+
if (lane == (Py_uhash_t)-1) {
8249+
return -1;
8250+
}
8251+
acc += lane * _PyTuple_HASH_XXPRIME_2;
8252+
acc = _PyTuple_HASH_XXROTATE(acc);
8253+
acc *= _PyTuple_HASH_XXPRIME_1;
8254+
8255+
/* Add input length, mangled to keep the historical value of hash(()). */
8256+
acc += len ^ (_PyTuple_HASH_XXPRIME_5 ^ 3527539UL);
8257+
8258+
if (acc == (Py_uhash_t)-1) {
8259+
acc = 1546275796;
8260+
}
8261+
return acc;
8262+
}
8263+
8264+
82318265
// Code copied from frozenset_hash()
82328266
static Py_hash_t
82338267
frozendict_hash(PyObject *op)
@@ -8244,17 +8278,11 @@ frozendict_hash(PyObject *op)
82448278
PyObject *key, *value; // borrowed refs
82458279
Py_ssize_t pos = 0;
82468280
while (PyDict_Next(op, &pos, &key, &value)) {
8247-
Py_hash_t key_hash = PyObject_Hash(key);
8248-
if (key_hash == -1) {
8249-
return -1;
8250-
}
8251-
hash ^= _shuffle_bits(key_hash);
8252-
8253-
Py_hash_t value_hash = PyObject_Hash(value);
8254-
if (value_hash == -1) {
8281+
Py_hash_t pair_hash = frozendict_pair_hash(key, value);
8282+
if (pair_hash == -1) {
82558283
return -1;
82568284
}
8257-
hash ^= _shuffle_bits(value_hash);
8285+
hash ^= _shuffle_bits(pair_hash);
82588286
}
82598287

82608288
/* Factor in the number of active entries */

0 commit comments

Comments
 (0)