Skip to content

Commit 8335fa1

Browse files
committed
Only look in cache for exact unicode strings.
Ensure we don't read the cache in the case the 'name' argument is a non-exact unicode string. It's possible to have an overridden `__eq__` method and it using `_PyUnicode_Equal()` in that case would be wrong.
1 parent 9c4e7c4 commit 8335fa1

File tree

1 file changed

+22
-12
lines changed

1 file changed

+22
-12
lines changed

Objects/typeobject.c

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,27 +47,37 @@ class object "PyObject *" "&PyBaseObject_Type"
4747
static inline unsigned int
4848
mcache_name_hash(PyTypeObject *type, PyObject *name)
4949
{
50-
unsigned int name_hash;
50+
Py_hash_t name_hash;
5151
#if Py_GIL_DISABLED
5252
// Cache misses are relatively more expensive for the free-threaded build.
5353
// So we use the unicode string hash and unicode compare for caching
5454
// names. This allows caching of non-interned strings.
55-
if (PyUnicode_CheckExact(name)) {
56-
name_hash = (unsigned int)_PyObject_HashFast(name);
57-
}
58-
else {
59-
name_hash = 0;
60-
}
55+
assert(PyUnicode_CheckExact(name));
56+
name_hash = _PyObject_HashFast(name);
57+
// should not fail to hash an exact unicode object
58+
assert(name_hash != -1);
6159
#else
6260
// Use the pointer value of the string for the hash and the compare. This
6361
// is faster but non-interned strings can't use the cache.
64-
name_hash = ((Py_ssize_t)(name)) >> 3;
62+
name_hash = ((Py_hash_t)(name)) >> 3;
6563
#endif
6664
unsigned int version = FT_ATOMIC_LOAD_UINT32_RELAXED((type)->tp_version_tag);
6765
return (((unsigned int)(version) ^ (unsigned int)(name_hash)) &
6866
((1 << MCACHE_SIZE_EXP) - 1));
6967
}
7068

69+
static inline struct type_cache_entry *
70+
mcache_get_entry(PyTypeObject *type, PyObject *name, struct type_cache *cache)
71+
{
72+
#ifdef Py_GIL_DISABLED
73+
if (!PyUnicode_CheckExact(name)) {
74+
return NULL;
75+
}
76+
#endif
77+
unsigned int h = mcache_name_hash(type, name);
78+
return &cache->hashtable[h];
79+
}
80+
7181
static inline int
7282
mcache_name_eq(PyObject *entry_name, PyObject *name)
7383
{
@@ -76,7 +86,7 @@ mcache_name_eq(PyObject *entry_name, PyObject *name)
7686
return 0;
7787
}
7888
assert(PyUnicode_CheckExact(entry_name));
79-
assert(PyUnicode_Check(name));
89+
assert(PyUnicode_CheckExact(name));
8090
return _PyUnicode_Equal(entry_name, name);
8191
#else
8292
return entry_name == name;
@@ -5754,12 +5764,11 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
57545764
unsigned int
57555765
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out)
57565766
{
5757-
unsigned int h = mcache_name_hash(type, name);
57585767
struct type_cache *cache = get_type_cache();
5759-
struct type_cache_entry *entry = &cache->hashtable[h];
5768+
struct type_cache_entry *entry = mcache_get_entry(type, name, cache);
57605769
#ifdef Py_GIL_DISABLED
57615770
// synchronize-with other writing threads by doing an acquire load on the sequence
5762-
while (1) {
5771+
while (entry != NULL) {
57635772
uint32_t sequence = _PySeqLock_BeginRead(&entry->sequence);
57645773
uint32_t entry_version = _Py_atomic_load_uint32_acquire(&entry->version);
57655774
uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag);
@@ -5837,6 +5846,7 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
58375846

58385847
if (has_version) {
58395848
#if Py_GIL_DISABLED
5849+
assert(entry != NULL);
58405850
update_cache_gil_disabled(entry, name, assigned_version, res);
58415851
#else
58425852
PyObject *old_value = update_cache(entry, name, assigned_version, res);

0 commit comments

Comments
 (0)