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]