gh-120860: Fix a few bugs in `type_setattro` error paths. (#120861)

Moves the logic to update the type's dictionary to its own function in order
to make the lock scoping more clear.

Also, ensure that `name` is decref'd on the error path.
This commit is contained in:
Sam Gross 2024-06-24 14:08:23 -04:00 committed by GitHub
parent 0153fd0940
commit dee63cb359
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 41 additions and 37 deletions

View File

@ -5656,6 +5656,42 @@ _Py_type_getattro(PyObject *type, PyObject *name)
return _Py_type_getattro_impl((PyTypeObject *)type, name, NULL);
}
static int
type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,
PyObject *value, PyObject **old_value)
{
// We don't want any re-entrancy between when we update the dict
// and call type_modified_unlocked, including running the destructor
// of the current value as it can observe the cache in an inconsistent
// state. Because we have an exact unicode and our dict has exact
// unicodes we know that this will all complete without releasing
// the locks.
if (_PyDict_GetItemRef_Unicode_LockHeld(dict, name, old_value) < 0) {
return -1;
}
/* Clear the VALID_VERSION flag of 'type' and all its
subclasses. This could possibly be unified with the
update_subclasses() recursion in update_slot(), but carefully:
they each have their own conditions on which to stop
recursing into subclasses. */
type_modified_unlocked(type);
if (_PyDict_SetItem_LockHeld(dict, name, value) < 0) {
PyErr_Format(PyExc_AttributeError,
"type object '%.50s' has no attribute '%U'",
((PyTypeObject*)type)->tp_name, name);
_PyObject_SetAttributeErrorContext((PyObject *)type, name);
return -1;
}
if (is_dunder_name(name)) {
return update_slot(type, name);
}
return 0;
}
static int
type_setattro(PyObject *self, PyObject *name, PyObject *value)
{
@ -5698,12 +5734,11 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_INLINE_VALUES));
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));
PyObject *old_value;
PyObject *old_value = NULL;
PyObject *descr = _PyType_LookupRef(metatype, name);
if (descr != NULL) {
descrsetfunc f = Py_TYPE(descr)->tp_descr_set;
if (f != NULL) {
old_value = NULL;
res = f(descr, (PyObject *)type, value);
goto done;
}
@ -5719,47 +5754,16 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
}
END_TYPE_LOCK();
if (dict == NULL) {
return -1;
res = -1;
goto done;
}
}
// We don't want any re-entrancy between when we update the dict
// and call type_modified_unlocked, including running the destructor
// of the current value as it can observe the cache in an inconsistent
// state. Because we have an exact unicode and our dict has exact
// unicodes we know that this will all complete without releasing
// the locks.
BEGIN_TYPE_DICT_LOCK(dict);
if (_PyDict_GetItemRef_Unicode_LockHeld((PyDictObject *)dict, name, &old_value) < 0) {
return -1;
}
/* Clear the VALID_VERSION flag of 'type' and all its
subclasses. This could possibly be unified with the
update_subclasses() recursion in update_slot(), but carefully:
they each have their own conditions on which to stop
recursing into subclasses. */
type_modified_unlocked(type);
res = _PyDict_SetItem_LockHeld((PyDictObject *)dict, name, value);
if (res == 0) {
if (is_dunder_name(name)) {
res = update_slot(type, name);
}
}
else if (PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Format(PyExc_AttributeError,
"type object '%.50s' has no attribute '%U'",
((PyTypeObject*)type)->tp_name, name);
_PyObject_SetAttributeErrorContext((PyObject *)type, name);
}
res = type_update_dict(type, (PyDictObject *)dict, name, value, &old_value);
assert(_PyType_CheckConsistency(type));
END_TYPE_DICT_LOCK();
done:
Py_DECREF(name);
Py_XDECREF(descr);