[3.14] gh-146270: Fix PyMember_SetOne(..., NULL) not being atomic (gh-148800) (#149460)

Fixes a sequential consistency bug whereby two threads that are deleting a struct member may observe both their deletions to be successful.

(cherry picked from commit 1bdfc0f253)

Co-authored-by: Daniele Parmeggiani <8658291+dpdani@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2026-05-06 16:16:36 +02:00 committed by GitHub
parent 5b33e9c176
commit 8a7f0570e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 21 deletions

View file

@ -161,19 +161,10 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
PyErr_SetString(PyExc_AttributeError, "readonly attribute");
return -1;
}
if (v == NULL) {
if (l->type == Py_T_OBJECT_EX) {
/* Check if the attribute is set. */
if (*(PyObject **)addr == NULL) {
PyErr_SetString(PyExc_AttributeError, l->name);
return -1;
}
}
else if (l->type != _Py_T_OBJECT) {
PyErr_SetString(PyExc_TypeError,
"can't delete numeric/char attribute");
return -1;
}
if (v == NULL && l->type != Py_T_OBJECT_EX && l->type != _Py_T_OBJECT) {
PyErr_SetString(PyExc_TypeError,
"can't delete numeric/char attribute");
return -1;
}
switch (l->type) {
case Py_T_BOOL:{
@ -324,6 +315,15 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
oldv = *(PyObject **)addr;
FT_ATOMIC_STORE_PTR_RELEASE(*(PyObject **)addr, Py_XNewRef(v));
Py_END_CRITICAL_SECTION();
if (v == NULL && oldv == NULL && l->type == Py_T_OBJECT_EX) {
// Raise an exception when attempting to delete an already deleted
// attribute.
// Differently from Py_T_OBJECT_EX, _Py_T_OBJECT does not raise an
// exception here (PyMember_GetOne will return Py_None instead of
// NULL).
PyErr_SetString(PyExc_AttributeError, l->name);
return -1;
}
Py_XDECREF(oldv);
break;
case Py_T_CHAR: {