https://github.com/python/cpython/commit/ecbd31ee390634a2d420b975c9acef747e10d00a
commit: ecbd31ee390634a2d420b975c9acef747e10d00a
branch: main
author: JasonMendoza2008 <[email protected]>
committer: vstinner <[email protected]>
date: 2026-06-15T11:31:04+02:00
summary:

gh-137759: Replace _PyObject_HashFast() with PyObject_Hash() in setobject.c 
(#137828)

Replace also _PyObject_HashFast() with PyObject_Hash()
in _collections._count_elements().

Rename _PyObject_HashFast() to _PyObject_HashDictKey(),
and mark it as Py_ALWAYS_INLINE.

Only use _PyObject_HashDictKey() on dictionaries.

files:
M Include/internal/pycore_object.h
M Modules/_collectionsmodule.c
M Objects/dictobject.c
M Objects/setobject.c
M Objects/typeobject.c

diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index dd7955c2770b99..98880cd83780dc 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -830,9 +830,14 @@ _PyObject_IS_GC(PyObject *obj)
             && (type->tp_is_gc == NULL || type->tp_is_gc(obj)));
 }
 
-// Fast inlined version of PyObject_Hash()
-static inline Py_hash_t
-_PyObject_HashFast(PyObject *op)
+// Fast inlined version of PyObject_Hash(). Dictionaries are very
+// likely to include string keys (class and instance attributes,
+// json, ...) so we include a fast path for strings.
+// This function should not be used in a collection if str is not
+// very likely, since it is slower than PyObject_Hash() on types
+// other than str. See gh-137759.
+static inline Py_ALWAYS_INLINE Py_hash_t
+_PyObject_HashDictKey(PyObject *op)
 {
     if (PyUnicode_CheckExact(op)) {
         Py_hash_t hash = PyUnstable_Unicode_GET_CACHED_HASH(op);
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index 5ca6362406a78b..e96a546a818d3d 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -2590,7 +2590,7 @@ _collections__count_elements_impl(PyObject *module, 
PyObject *mapping,
             if (key == NULL)
                 break;
 
-            hash = _PyObject_HashFast(key);
+            hash = _PyObject_HashDictKey(key);
             if (hash == -1) {
                 goto done;
             }
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 0a6d0f9199641e..ac2f210d023487 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -2384,7 +2384,7 @@ dict_getitem(PyObject *op, PyObject *key, const char 
*warnmsg)
     }
     PyDictObject *mp = (PyDictObject *)op;
 
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         PyErr_FormatUnraisable(warnmsg);
         return NULL;
@@ -2456,7 +2456,7 @@ _PyDict_LookupIndexAndValue(PyDictObject *mp, PyObject 
*key, PyObject **value)
     assert(PyDict_CheckExact((PyObject*)mp));
     assert(PyUnicode_CheckExact(key));
 
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type((PyObject*)mp, key);
         return -1;
@@ -2560,7 +2560,7 @@ PyDict_GetItemRef(PyObject *op, PyObject *key, PyObject 
**result)
         return -1;
     }
 
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(op, key);
         *result = NULL;
@@ -2576,7 +2576,7 @@ _PyDict_GetItemRef_Unicode_LockHeld(PyDictObject *op, 
PyObject *key, PyObject **
     ASSERT_DICT_LOCKED(op);
     assert(PyUnicode_CheckExact(key));
 
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type((PyObject*)op, key);
         *result = NULL;
@@ -2614,7 +2614,7 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key)
         PyErr_BadInternalCall();
         return NULL;
     }
-    hash = _PyObject_HashFast(key);
+    hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(op, key);
         return NULL;
@@ -2673,7 +2673,7 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject 
*builtins, PyObject *key)
     Py_hash_t hash;
     PyObject *value;
 
-    hash = _PyObject_HashFast(key);
+    hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         return NULL;
     }
@@ -2697,7 +2697,7 @@ _PyDict_LoadGlobalStackRef(PyDictObject *globals, 
PyDictObject *builtins, PyObje
     Py_ssize_t ix;
     Py_hash_t hash;
 
-    hash = _PyObject_HashFast(key);
+    hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         *res = PyStackRef_NULL;
         return;
@@ -2774,7 +2774,7 @@ setitem_take2_lock_held_known_hash(PyDictObject *mp, 
PyObject *key, PyObject *va
 static int
 setitem_take2_lock_held(PyDictObject *mp, PyObject *key, PyObject *value)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type((PyObject*)mp, key);
         Py_DECREF(key);
@@ -2952,7 +2952,7 @@ int
 PyDict_DelItem(PyObject *op, PyObject *key)
 {
     assert(key);
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(op, key);
         return -1;
@@ -3296,7 +3296,7 @@ pop_lock_held(PyObject *op, PyObject *key, PyObject 
**result)
         return 0;
     }
 
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(op, key);
         if (result) {
@@ -3734,7 +3734,7 @@ _PyDict_SubscriptKnownHash(PyObject *self, PyObject *key, 
Py_hash_t hash)
 PyObject *
 _PyDict_Subscript(PyObject *self, PyObject *key)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(self, key);
         return NULL;
@@ -4686,7 +4686,7 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject 
*default_value)
     Py_hash_t hash;
     Py_ssize_t ix;
 
-    hash = _PyObject_HashFast(key);
+    hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type((PyObject*)self, key);
         return NULL;
@@ -4723,7 +4723,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, 
PyObject *default_valu
     Py_hash_t hash;
     Py_ssize_t ix;
 
-    hash = _PyObject_HashFast(key);
+    hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(d, key);
         if (result) {
@@ -5165,7 +5165,7 @@ static PyMethodDef mapp_methods[] = {
 static int
 dict_contains(PyObject *op, PyObject *key)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = _PyObject_HashDictKey(key);
     if (hash == -1) {
         dict_unhashable_type(op, key);
         return -1;
@@ -7298,7 +7298,7 @@ _PyDict_SetItem_LockHeld(PyDictObject *dict, PyObject 
*name, PyObject *value)
     }
 
     if (value == NULL) {
-        Py_hash_t hash = _PyObject_HashFast(name);
+        Py_hash_t hash = _PyObject_HashDictKey(name);
         if (hash == -1) {
             dict_unhashable_type((PyObject*)dict, name);
             return -1;
diff --git a/Objects/setobject.c b/Objects/setobject.c
index 50c5f66c2afc1b..d198794849f0d1 100644
--- a/Objects/setobject.c
+++ b/Objects/setobject.c
@@ -361,7 +361,7 @@ set_unhashable_type(PyObject *key)
 int
 _PySet_AddTakeRef(PySetObject *so, PyObject *key)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         set_unhashable_type(key);
         Py_DECREF(key);
@@ -600,7 +600,7 @@ set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t 
hash)
 static int
 set_add_key(PySetObject *so, PyObject *key)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         set_unhashable_type(key);
         return -1;
@@ -611,7 +611,7 @@ set_add_key(PySetObject *so, PyObject *key)
 static int
 set_contains_key(PySetObject *so, PyObject *key)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         set_unhashable_type(key);
         return -1;
@@ -622,7 +622,7 @@ set_contains_key(PySetObject *so, PyObject *key)
 static int
 set_discard_key(PySetObject *so, PyObject *key)
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         set_unhashable_type(key);
         return -1;
@@ -2514,7 +2514,7 @@ _PySet_Contains(PySetObject *so, PyObject *key)
 {
     assert(so);
 
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) {
             set_unhashable_type(key);
@@ -2574,7 +2574,7 @@ static PyObject *
 frozenset___contains___impl(PySetObject *so, PyObject *key)
 /*[clinic end generated code: output=2301ed91bc3a6dd5 input=2f04922a98d8bab7]*/
 {
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) {
             set_unhashable_type(key);
@@ -3039,7 +3039,7 @@ PySet_Contains(PyObject *anyset, PyObject *key)
     }
 
     PySetObject *so = (PySetObject *)anyset;
-    Py_hash_t hash = _PyObject_HashFast(key);
+    Py_hash_t hash = PyObject_Hash(key);
     if (hash == -1) {
         set_unhashable_type(key);
         return -1;
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 388e8891c9c739..865c32f02b13b4 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3793,7 +3793,7 @@ solid_base(PyTypeObject *type)
 // or when __bases__ is re-assigned.  Since the slots are read without atomic
 // operations and without locking, we can only safely update them while the
 // world is stopped.  However, with the world stopped, we are very limited on
-// which APIs can be safely used.  For example, calling _PyObject_HashFast()
+// which APIs can be safely used.  For example, calling _PyObject_HashDictKey()
 // or _PyDict_GetItemRef_KnownHash() are not safe and can potentially cause
 // deadlocks.  Hashing can be re-entrant and _PyDict_GetItemRef_KnownHash can
 // acquire a lock if the dictionary is not owned by the current thread, to
@@ -6134,7 +6134,7 @@ PyObject_GetItemData(PyObject *obj)
 static int
 find_name_in_mro(PyTypeObject *type, PyObject *name, _PyStackRef *out)
 {
-    Py_hash_t hash = _PyObject_HashFast(name);
+    Py_hash_t hash = _PyObject_HashDictKey(name);
     if (hash == -1) {
         PyErr_Clear();
         return -1;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to