From: JasonMendoza2008 <56092290+JasonMendoza2008@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:31:04 +0000 (+0200) Subject: gh-137759: Replace _PyObject_HashFast() with PyObject_Hash() in setobject.c (#137828) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ecbd31ee390634a2d420b975c9acef747e10d00a;p=thirdparty%2FPython%2Fcpython.git 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. --- diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index dd7955c2770b..98880cd83780 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 5ca6362406a7..e96a546a818d 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 0a6d0f919964..ac2f210d0234 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 50c5f66c2afc..d198794849f0 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 388e8891c9c7..865c32f02b13 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;