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);
|
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
|
static int
|
||||||
type_setattro(PyObject *self, PyObject *name, PyObject *value)
|
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_INLINE_VALUES));
|
||||||
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));
|
assert(!_PyType_HasFeature(metatype, Py_TPFLAGS_MANAGED_DICT));
|
||||||
|
|
||||||
PyObject *old_value;
|
PyObject *old_value = NULL;
|
||||||
PyObject *descr = _PyType_LookupRef(metatype, name);
|
PyObject *descr = _PyType_LookupRef(metatype, name);
|
||||||
if (descr != NULL) {
|
if (descr != NULL) {
|
||||||
descrsetfunc f = Py_TYPE(descr)->tp_descr_set;
|
descrsetfunc f = Py_TYPE(descr)->tp_descr_set;
|
||||||
if (f != NULL) {
|
if (f != NULL) {
|
||||||
old_value = NULL;
|
|
||||||
res = f(descr, (PyObject *)type, value);
|
res = f(descr, (PyObject *)type, value);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
@ -5719,47 +5754,16 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
|
||||||
}
|
}
|
||||||
END_TYPE_LOCK();
|
END_TYPE_LOCK();
|
||||||
if (dict == NULL) {
|
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);
|
BEGIN_TYPE_DICT_LOCK(dict);
|
||||||
|
res = type_update_dict(type, (PyDictObject *)dict, name, value, &old_value);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(_PyType_CheckConsistency(type));
|
assert(_PyType_CheckConsistency(type));
|
||||||
|
|
||||||
END_TYPE_DICT_LOCK();
|
END_TYPE_DICT_LOCK();
|
||||||
|
|
||||||
done:
|
done:
|
||||||
Py_DECREF(name);
|
Py_DECREF(name);
|
||||||
Py_XDECREF(descr);
|
Py_XDECREF(descr);
|
||||||
|
|
Loading…
Reference in New Issue