From 197615e922d523f1b77b65657a866ad3c3172e67 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sun, 6 Apr 2025 17:08:06 +0500 Subject: [PATCH 01/14] Prebuild mro_dict for find_name_in_mro --- Objects/typeobject.c | 59 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 63e153e6715c17..14e6972a3680d0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5656,6 +5656,26 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) return res; } +static PyObject * +find_name_in_mro_new(PyObject *mro_dict, PyObject *name, int *error) +{ + ASSERT_TYPE_LOCK_HELD(); + + Py_hash_t hash = _PyObject_HashFast(name); + if (hash == -1) { + *error = -1; + return NULL; + } + + PyObject *res = NULL; + if (_PyDict_GetItemRef_KnownHash((PyDictObject *)mro_dict, name, hash, &res) < 0) { + *error = -1; + } else { + *error = 0; + } + return res; +} + /* Check if the "readied" PyUnicode name is a double-underscore special name. */ static int @@ -11139,7 +11159,7 @@ resolve_slotdups(PyTypeObject *type, PyObject *name) * because that's convenient for fixup_slot_dispatchers(). This function never * sets an exception: if an internal error happens (unlikely), it's ignored. */ static pytype_slotdef * -update_one_slot(PyTypeObject *type, pytype_slotdef *p) +update_one_slot(PyTypeObject *type, pytype_slotdef *p, PyObject *mro_dict) { ASSERT_TYPE_LOCK_HELD(); @@ -11170,7 +11190,11 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p) assert(!PyErr_Occurred()); do { /* Use faster uncached lookup as we won't get any cache hits during type setup. */ - descr = find_name_in_mro(type, p->name_strobj, &error); + if (mro_dict == NULL) { + descr = find_name_in_mro(type, p->name_strobj, &error); + } else { + descr = find_name_in_mro_new(mro_dict, p->name_strobj, &error); + } if (descr == NULL) { if (error == -1) { /* It is unlikely but not impossible that there has been an exception @@ -11261,7 +11285,7 @@ update_slots_callback(PyTypeObject *type, void *data) pytype_slotdef **pp = (pytype_slotdef **)data; for (; *pp; pp++) { - update_one_slot(type, *pp); + update_one_slot(type, *pp, NULL); } return 0; } @@ -11314,11 +11338,38 @@ fixup_slot_dispatchers(PyTypeObject *type) // where we'd like to assert that the type is locked. BEGIN_TYPE_LOCK(); + PyObject *mro = lookup_tp_mro(type); + assert(mro); + + PyObject *mro_dict = NULL; + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *base = PyTuple_GET_ITEM(mro, n-i-1); + PyObject *dict = lookup_tp_dict(_PyType_CAST(base)); + assert(dict && PyDict_Check(dict)); + + if (i == 0) { + mro_dict = PyDict_Copy(dict); + if (!mro_dict) { + PyErr_Clear(); + break; + } + } else { + if (PyDict_Merge(mro_dict, dict, 1) < 0) { + Py_CLEAR(mro_dict); + PyErr_Clear(); + break; + } + } + } + assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { - p = update_one_slot(type, p); + p = update_one_slot(type, p, mro_dict); } + Py_XDECREF(mro_dict); + END_TYPE_LOCK(); } From b7842e01a43c0b2b4d6095c8021665679165a352 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 02:56:00 +0500 Subject: [PATCH 02/14] Simplify find_name_in_mro_new and add some comments --- Objects/typeobject.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 14e6972a3680d0..7fe39d59cd5489 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5656,19 +5656,16 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) return res; } +/* Internal API to look for a name through the prebuilt MRO dict. + This returns a strong reference, and might set an exception. + 'error' is set to: -1: error with exception; 0: ok */ static PyObject * find_name_in_mro_new(PyObject *mro_dict, PyObject *name, int *error) { ASSERT_TYPE_LOCK_HELD(); - Py_hash_t hash = _PyObject_HashFast(name); - if (hash == -1) { - *error = -1; - return NULL; - } - PyObject *res = NULL; - if (_PyDict_GetItemRef_KnownHash((PyDictObject *)mro_dict, name, hash, &res) < 0) { + if (PyDict_GetItemRef(mro_dict, name, &res) < 0) { *error = -1; } else { *error = 0; @@ -11341,6 +11338,10 @@ fixup_slot_dispatchers(PyTypeObject *type) PyObject *mro = lookup_tp_mro(type); assert(mro); + // Try to prebuild MRO dict. If we fails then clear mro_dict and + // reset error flag because we don't expect any exceptions. If + // fails to prebuild MRO dict then update_on_slot will use + // previous version of find_name_in_mro. PyObject *mro_dict = NULL; Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) { From a7a978ca4b691c0d499ddd47bde754dcfe44ee15 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Thu, 17 Apr 2025 03:03:14 +0500 Subject: [PATCH 03/14] Fix comment for find_namd_in_mro becase it returns strong ref now --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7fe39d59cd5489..8fdb91cce937a0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5604,7 +5604,7 @@ PyObject_GetItemData(PyObject *obj) } /* Internal API to look for a name through the MRO, bypassing the method cache. - This returns a borrowed reference, and might set an exception. + This returns a strong reference, and might set an exception. 'error' is set to: -1: error with exception; 1: error without exception; 0: ok */ static PyObject * find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) From a64d3a0ee5011fbf13301d829b811513b7e03638 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Fri, 18 Apr 2025 23:59:24 +0500 Subject: [PATCH 04/14] Allow fixup_slot_dispatchers fails --- Objects/typeobject.c | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7b99ca64695a32..91546a82db3a46 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3621,7 +3621,7 @@ static void object_dealloc(PyObject *); static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *); static int object_init(PyObject *, PyObject *, PyObject *); static int update_slot(PyTypeObject *, PyObject *); -static void fixup_slot_dispatchers(PyTypeObject *); +static int fixup_slot_dispatchers(PyTypeObject *); static int type_new_set_names(PyTypeObject *); static int type_new_init_subclass(PyTypeObject *, PyObject *); @@ -4577,7 +4577,9 @@ type_new_impl(type_new_ctx *ctx) } // Put the proper slots in place - fixup_slot_dispatchers(type); + if (fixup_slot_dispatchers(type) < 0) { + goto error; + } if (!_PyDict_HasOnlyStringKeys(type->tp_dict)) { if (PyErr_WarnFormat( @@ -11329,10 +11331,12 @@ update_slot(PyTypeObject *type, PyObject *name) /* Store the proper functions in the slot dispatches at class (type) definition time, based upon which operations the class overrides in its - dict. */ -static void + dict. Returns -1 and exception set on error or 0 otherwise.*/ +static int fixup_slot_dispatchers(PyTypeObject *type) { + int res = 0; + // This lock isn't strictly necessary because the type has not been // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro // where we'd like to assert that the type is locked. @@ -11341,10 +11345,11 @@ fixup_slot_dispatchers(PyTypeObject *type) PyObject *mro = lookup_tp_mro(type); assert(mro); - // Try to prebuild MRO dict. If we fails then clear mro_dict and - // reset error flag because we don't expect any exceptions. If - // fails to prebuild MRO dict then update_on_slot will use - // previous version of find_name_in_mro. + // Try to prebuild MRO dict. We build it in bottom-top manner, + // from bottom base to the top one, because the bottommost base + // has more items then other and copying it is preferable than + // merging. + // If we fails, then stop init type. PyObject *mro_dict = NULL; Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) { @@ -11355,26 +11360,31 @@ fixup_slot_dispatchers(PyTypeObject *type) if (i == 0) { mro_dict = PyDict_Copy(dict); if (!mro_dict) { - PyErr_Clear(); - break; + res = -1; + goto finish; } } else { if (PyDict_Merge(mro_dict, dict, 1) < 0) { - Py_CLEAR(mro_dict); - PyErr_Clear(); - break; + res = -1; + goto finish; } } } + assert(!res); + assert(mro_dict); assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { p = update_one_slot(type, p, mro_dict); } + assert(!PyErr_Occurred()); + +finish: Py_XDECREF(mro_dict); END_TYPE_LOCK(); + return res; } static void From 88a19d5c073b8467e7216b280b63e16e98658a75 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 19 Apr 2025 02:02:49 +0500 Subject: [PATCH 05/14] Revert "Allow fixup_slot_dispatchers fails" This reverts commit a64d3a0ee5011fbf13301d829b811513b7e03638. --- Objects/typeobject.c | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e1eb26813ce978..0db7c4df9bc4c4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3621,7 +3621,7 @@ static void object_dealloc(PyObject *); static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *); static int object_init(PyObject *, PyObject *, PyObject *); static int update_slot(PyTypeObject *, PyObject *); -static int fixup_slot_dispatchers(PyTypeObject *); +static void fixup_slot_dispatchers(PyTypeObject *); static int type_new_set_names(PyTypeObject *); static int type_new_init_subclass(PyTypeObject *, PyObject *); @@ -4577,9 +4577,7 @@ type_new_impl(type_new_ctx *ctx) } // Put the proper slots in place - if (fixup_slot_dispatchers(type) < 0) { - goto error; - } + fixup_slot_dispatchers(type); if (!_PyDict_HasOnlyStringKeys(type->tp_dict)) { if (PyErr_WarnFormat( @@ -11333,12 +11331,10 @@ update_slot(PyTypeObject *type, PyObject *name) /* Store the proper functions in the slot dispatches at class (type) definition time, based upon which operations the class overrides in its - dict. Returns -1 and exception set on error or 0 otherwise.*/ -static int + dict. */ +static void fixup_slot_dispatchers(PyTypeObject *type) { - int res = 0; - // This lock isn't strictly necessary because the type has not been // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro // where we'd like to assert that the type is locked. @@ -11347,11 +11343,10 @@ fixup_slot_dispatchers(PyTypeObject *type) PyObject *mro = lookup_tp_mro(type); assert(mro); - // Try to prebuild MRO dict. We build it in bottom-top manner, - // from bottom base to the top one, because the bottommost base - // has more items then other and copying it is preferable than - // merging. - // If we fails, then stop init type. + // Try to prebuild MRO dict. If we fails then clear mro_dict and + // reset error flag because we don't expect any exceptions. If + // fails to prebuild MRO dict then update_on_slot will use + // previous version of find_name_in_mro. PyObject *mro_dict = NULL; Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) { @@ -11362,31 +11357,26 @@ fixup_slot_dispatchers(PyTypeObject *type) if (i == 0) { mro_dict = PyDict_Copy(dict); if (!mro_dict) { - res = -1; - goto finish; + PyErr_Clear(); + break; } } else { if (PyDict_Merge(mro_dict, dict, 1) < 0) { - res = -1; - goto finish; + Py_CLEAR(mro_dict); + PyErr_Clear(); + break; } } } - assert(!res); - assert(mro_dict); assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { p = update_one_slot(type, p, mro_dict); } - assert(!PyErr_Occurred()); - -finish: Py_XDECREF(mro_dict); END_TYPE_LOCK(); - return res; } static void From 860fbe739a7c508062b3d1a0bccaf3c239be0148 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 19 Apr 2025 02:06:37 +0500 Subject: [PATCH 06/14] Update comment about prebuild MRO-dict --- Objects/typeobject.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0db7c4df9bc4c4..d67b49d6efb283 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11343,10 +11343,13 @@ fixup_slot_dispatchers(PyTypeObject *type) PyObject *mro = lookup_tp_mro(type); assert(mro); - // Try to prebuild MRO dict. If we fails then clear mro_dict and - // reset error flag because we don't expect any exceptions. If - // fails to prebuild MRO dict then update_on_slot will use - // previous version of find_name_in_mro. + // Try to prebuild MRO dict. We build it in bottom-top manner, + // from bottom base to the top one, because the bottommost base + // has more items then other and copying it is preferable than + // merging. + // If we fails then clear mro_dict and reset error flag because + // we don't expect any exceptions. If fails to prebuild MRO dict + // then update_on_slot will use previous version of find_name_in_mro. PyObject *mro_dict = NULL; Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) { From d46f493fb52acc6189aa91e7e86fdfa668bcc1d1 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 22 Apr 2025 23:46:54 +0500 Subject: [PATCH 07/14] Allow fixup_slot_dispatchers fails --- Objects/typeobject.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index d67b49d6efb283..a6ec87cf27b334 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3621,7 +3621,7 @@ static void object_dealloc(PyObject *); static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *); static int object_init(PyObject *, PyObject *, PyObject *); static int update_slot(PyTypeObject *, PyObject *); -static void fixup_slot_dispatchers(PyTypeObject *); +static int fixup_slot_dispatchers(PyTypeObject *); static int type_new_set_names(PyTypeObject *); static int type_new_init_subclass(PyTypeObject *, PyObject *); @@ -4577,7 +4577,9 @@ type_new_impl(type_new_ctx *ctx) } // Put the proper slots in place - fixup_slot_dispatchers(type); + if (fixup_slot_dispatchers(type) < 0) { + goto error; + } if (!_PyDict_HasOnlyStringKeys(type->tp_dict)) { if (PyErr_WarnFormat( @@ -11331,25 +11333,24 @@ update_slot(PyTypeObject *type, PyObject *name) /* Store the proper functions in the slot dispatches at class (type) definition time, based upon which operations the class overrides in its - dict. */ -static void + dict. Returns -1 and exception set on error or 0 otherwise.*/ +static int fixup_slot_dispatchers(PyTypeObject *type) { + int res = 0; + // This lock isn't strictly necessary because the type has not been // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro // where we'd like to assert that the type is locked. BEGIN_TYPE_LOCK(); - PyObject *mro = lookup_tp_mro(type); - assert(mro); + PyObject *mro = Py_NewRef(lookup_tp_mro(type)); // Try to prebuild MRO dict. We build it in bottom-top manner, // from bottom base to the top one, because the bottommost base // has more items then other and copying it is preferable than // merging. - // If we fails then clear mro_dict and reset error flag because - // we don't expect any exceptions. If fails to prebuild MRO dict - // then update_on_slot will use previous version of find_name_in_mro. + // If we fails, then stop init type. PyObject *mro_dict = NULL; Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) { @@ -11360,26 +11361,32 @@ fixup_slot_dispatchers(PyTypeObject *type) if (i == 0) { mro_dict = PyDict_Copy(dict); if (!mro_dict) { - PyErr_Clear(); - break; + res = -1; + goto finish; } } else { if (PyDict_Merge(mro_dict, dict, 1) < 0) { - Py_CLEAR(mro_dict); - PyErr_Clear(); - break; + res = -1; + goto finish; } } } + assert(!res); + assert(mro_dict); assert(!PyErr_Occurred()); for (pytype_slotdef *p = slotdefs; p->name; ) { p = update_one_slot(type, p, mro_dict); } + assert(!PyErr_Occurred()); + +finish: Py_XDECREF(mro_dict); + Py_DECREF(mro); END_TYPE_LOCK(); + return res; } static void From 8f7750ef04db99ef42b3ca89d190e0fe83ff3425 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 22 Apr 2025 23:54:25 +0500 Subject: [PATCH 08/14] Fix test_type_lookup_mro_reference test because it raises exception while type initialized - in the old realisation this exception swallowed and this base not checked while finding in mro. so we don't change observed behavior with this change. --- Lib/test/test_descr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 8e9d44a583cb31..50adb32c937f48 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -5236,7 +5236,10 @@ def __hash__(self): return hash('mykey') def __eq__(self, other): - X.__bases__ = (Base2,) + try: + X.__bases__ = (Base2,) + except NameError: + pass class Base(object): mykey = 'from Base' From 7f4c9c7a53babab866b7b41d1af0336824a14e8e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 23 Apr 2025 01:36:56 +0500 Subject: [PATCH 09/14] Update comment about building MRO dict Co-authored-by: Victor Stinner --- Objects/typeobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b5b9f95cbd62e9..afe13fabd08cdd 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11351,7 +11351,7 @@ fixup_slot_dispatchers(PyTypeObject *type) PyObject *mro = Py_NewRef(lookup_tp_mro(type)); - // Try to prebuild MRO dict. We build it in bottom-top manner, + // Build MRO dict. We build it in bottom-top manner, // from bottom base to the top one, because the bottommost base // has more items then other and copying it is preferable than // merging. From 26bafbf5036307f2cb24f31407d3c2e532bdcbd6 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 29 Apr 2025 10:23:38 +0500 Subject: [PATCH 10/14] Remove END_TYPE_LOCK from fixup_slot_dispatchers --- Objects/typeobject.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 12264111526c41..345a4b8e6ba2ab 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11502,8 +11502,6 @@ fixup_slot_dispatchers(PyTypeObject *type) finish: Py_XDECREF(mro_dict); Py_DECREF(mro); - - // END_TYPE_LOCK(); return res; } From 74eabaf9fe9e6b357ec1b4e00de00b6f8b68e18f Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 29 Apr 2025 11:44:09 +0500 Subject: [PATCH 11/14] Trying to fix build --- Objects/typeobject.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 345a4b8e6ba2ab..376c299d397734 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -11460,7 +11460,10 @@ fixup_slot_dispatchers(PyTypeObject *type) { int res = 0; - ASSERT_WORLD_STOPPED_OR_NEW_TYPE(type); + // This lock isn't strictly necessary because the type has not been + // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro + // where we'd like to assert that the type is locked. + BEGIN_TYPE_LOCK(); PyObject *mro = Py_NewRef(lookup_tp_mro(type)); @@ -11502,6 +11505,8 @@ fixup_slot_dispatchers(PyTypeObject *type) finish: Py_XDECREF(mro_dict); Py_DECREF(mro); + + END_TYPE_LOCK(); return res; } From 63ab0441e40e775d72580852a30cba7fc0718a5c Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 29 Apr 2025 11:51:09 +0500 Subject: [PATCH 12/14] Fix build --- Objects/typeobject.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 376c299d397734..584e1a0f3f9e6b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5738,9 +5738,9 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) This returns a strong reference, and might set an exception. 'error' is set to: -1: error with exception; 0: ok */ static PyObject * -find_name_in_mro_new(PyObject *mro_dict, PyObject *name, int *error) +find_name_in_mro_new(PyTypeObject *type, PyObject *mro_dict, PyObject *name, int *error) { - ASSERT_TYPE_LOCK_HELD(); + ASSERT_WORLD_STOPPED_OR_NEW_TYPE(type); PyObject *res = NULL; if (PyDict_GetItemRef(mro_dict, name, &res) < 0) { @@ -11311,7 +11311,7 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p, PyObject *mro_dict) if (mro_dict == NULL) { descr = find_name_in_mro(type, p->name_strobj, &error); } else { - descr = find_name_in_mro_new(mro_dict, p->name_strobj, &error); + descr = find_name_in_mro_new(type, mro_dict, p->name_strobj, &error); } if (descr == NULL) { if (error == -1) { @@ -11458,12 +11458,9 @@ update_slot(PyTypeObject *type, PyObject *name) static int fixup_slot_dispatchers(PyTypeObject *type) { - int res = 0; + ASSERT_WORLD_STOPPED_OR_NEW_TYPE(type); - // This lock isn't strictly necessary because the type has not been - // exposed to anyone else yet, but update_ont_slot calls find_name_in_mro - // where we'd like to assert that the type is locked. - BEGIN_TYPE_LOCK(); + int res = 0; PyObject *mro = Py_NewRef(lookup_tp_mro(type)); @@ -11505,8 +11502,6 @@ fixup_slot_dispatchers(PyTypeObject *type) finish: Py_XDECREF(mro_dict); Py_DECREF(mro); - - END_TYPE_LOCK(); return res; } From b9a1208bcac5bf2679425229a102ed56abc6a121 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 29 Apr 2025 14:32:49 +0500 Subject: [PATCH 13/14] Fix merge conflicts --- Objects/typeobject.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index afe13fabd08cdd..75163dbbe13504 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5781,7 +5781,7 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef // synchronize-with other writing threads by doing an acquire load on the sequence while (1) { uint32_t sequence = _PySeqLock_BeginRead(&entry->sequence); - uint32_t entry_version = _Py_atomic_load_uint32_relaxed(&entry->version); + uint32_t entry_version = _Py_atomic_load_uint32_acquire(&entry->version); uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag); if (entry_version == type_version && _Py_atomic_load_ptr_relaxed(&entry->name) == name) { @@ -5828,11 +5828,14 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef int has_version = 0; unsigned int assigned_version = 0; BEGIN_TYPE_LOCK(); - res = find_name_in_mro(type, name, &error); + // We must assign the version before doing the lookup. If + // find_name_in_mro() blocks and releases the critical section + // then the type version can change. if (MCACHE_CACHEABLE_NAME(name)) { has_version = assign_version_tag(interp, type); assigned_version = type->tp_version_tag; } + res = find_name_in_mro(type, name, &error); END_TYPE_LOCK(); /* Only put NULL results into cache if there was no error. */ From 39f987b16e41e7630c54a20b561c03e6c4d07347 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Tue, 29 Apr 2025 14:34:32 +0500 Subject: [PATCH 14/14] Fix merge conflicts --- Objects/typeobject.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 75163dbbe13504..4617e748716b18 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -5697,7 +5697,6 @@ is_dunder_name(PyObject *name) static PyObject * update_cache(struct type_cache_entry *entry, PyObject *name, unsigned int version_tag, PyObject *value) { - _Py_atomic_store_uint32_relaxed(&entry->version, version_tag); _Py_atomic_store_ptr_relaxed(&entry->value, value); /* borrowed */ assert(_PyASCIIObject_CAST(name)->hash != -1); OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name); @@ -5705,6 +5704,12 @@ update_cache(struct type_cache_entry *entry, PyObject *name, unsigned int versio // exact unicode object or Py_None so it's safe to do so. PyObject *old_name = entry->name; _Py_atomic_store_ptr_relaxed(&entry->name, Py_NewRef(name)); + // We must write the version last to avoid _Py_TryXGetStackRef() + // operating on an invalid (already deallocated) value inside + // _PyType_LookupRefAndVersion(). If we write the version first then a + // reader could pass the "entry_version == type_version" check but could + // be using the old entry value. + _Py_atomic_store_uint32_release(&entry->version, version_tag); return old_name; }