mirror of https://github.com/python/cpython
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:
parent
0153fd0940
commit
dee63cb359
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue