mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
gh-124470: Fix crash when reading from object instance dictionary while replacing it (#122489)
Delay free a dictionary when replacing it
This commit is contained in:
parent
3926842117
commit
bf542f8bb9
7 changed files with 294 additions and 90 deletions
|
|
@ -7087,51 +7087,146 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
|
|||
}
|
||||
}
|
||||
|
||||
int
|
||||
_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
|
||||
#ifdef Py_GIL_DISABLED
|
||||
|
||||
// Trys and sets the dictionary for an object in the easy case when our current
|
||||
// dictionary is either completely not materialized or is a dictionary which
|
||||
// does not point at the inline values.
|
||||
static bool
|
||||
try_set_dict_inline_only_or_other_dict(PyObject *obj, PyObject *new_dict, PyDictObject **cur_dict)
|
||||
{
|
||||
bool replaced = false;
|
||||
Py_BEGIN_CRITICAL_SECTION(obj);
|
||||
|
||||
PyDictObject *dict = *cur_dict = _PyObject_GetManagedDict(obj);
|
||||
if (dict == NULL) {
|
||||
// We only have inline values, we can just completely replace them.
|
||||
set_dict_inline_values(obj, (PyDictObject *)new_dict);
|
||||
replaced = true;
|
||||
goto exit_lock;
|
||||
}
|
||||
|
||||
if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) != _PyObject_InlineValues(obj)) {
|
||||
// We have a materialized dict which doesn't point at the inline values,
|
||||
// We get to simply swap dictionaries and free the old dictionary.
|
||||
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
|
||||
(PyDictObject *)Py_XNewRef(new_dict));
|
||||
replaced = true;
|
||||
goto exit_lock;
|
||||
}
|
||||
else {
|
||||
// We have inline values, we need to lock the dict and the object
|
||||
// at the same time to safely dematerialize them. To do that while releasing
|
||||
// the object lock we need a strong reference to the current dictionary.
|
||||
Py_INCREF(dict);
|
||||
}
|
||||
exit_lock:
|
||||
Py_END_CRITICAL_SECTION();
|
||||
return replaced;
|
||||
}
|
||||
|
||||
// Replaces a dictionary that is probably the dictionary which has been
|
||||
// materialized and points at the inline values. We could have raced
|
||||
// and replaced it with another dictionary though.
|
||||
static int
|
||||
replace_dict_probably_inline_materialized(PyObject *obj, PyDictObject *inline_dict,
|
||||
PyDictObject *cur_dict, PyObject *new_dict)
|
||||
{
|
||||
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
|
||||
|
||||
if (cur_dict == inline_dict) {
|
||||
assert(FT_ATOMIC_LOAD_PTR_RELAXED(inline_dict->ma_values) == _PyObject_InlineValues(obj));
|
||||
|
||||
int err = _PyDict_DetachFromObject(inline_dict, obj);
|
||||
if (err != 0) {
|
||||
assert(new_dict == NULL);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
|
||||
(PyDictObject *)Py_XNewRef(new_dict));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
decref_maybe_delay(PyObject *obj, bool delay)
|
||||
{
|
||||
if (delay) {
|
||||
_PyObject_XDecRefDelayed(obj);
|
||||
}
|
||||
else {
|
||||
Py_XDECREF(obj);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear)
|
||||
{
|
||||
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
|
||||
#ifndef NDEBUG
|
||||
Py_BEGIN_CRITICAL_SECTION(obj);
|
||||
assert(_PyObject_InlineValuesConsistencyCheck(obj));
|
||||
Py_END_CRITICAL_SECTION();
|
||||
#endif
|
||||
int err = 0;
|
||||
PyTypeObject *tp = Py_TYPE(obj);
|
||||
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyDictObject *prev_dict;
|
||||
if (!try_set_dict_inline_only_or_other_dict(obj, new_dict, &prev_dict)) {
|
||||
// We had a materialized dictionary which pointed at the inline
|
||||
// values. We need to lock both the object and the dict at the
|
||||
// same time to safely replace it. We can't merely lock the dictionary
|
||||
// while the object is locked because it could suspend the object lock.
|
||||
PyDictObject *cur_dict;
|
||||
|
||||
assert(prev_dict != NULL);
|
||||
Py_BEGIN_CRITICAL_SECTION2(obj, prev_dict);
|
||||
|
||||
// We could have had another thread race in between the call to
|
||||
// try_set_dict_inline_only_or_other_dict where we locked the object
|
||||
// and when we unlocked and re-locked the dictionary.
|
||||
cur_dict = _PyObject_GetManagedDict(obj);
|
||||
|
||||
err = replace_dict_probably_inline_materialized(obj, prev_dict,
|
||||
cur_dict, new_dict);
|
||||
|
||||
Py_END_CRITICAL_SECTION2();
|
||||
|
||||
// Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict
|
||||
// while the object was locked
|
||||
decref_maybe_delay((PyObject *)prev_dict,
|
||||
!clear && prev_dict != cur_dict);
|
||||
if (err != 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
prev_dict = cur_dict;
|
||||
}
|
||||
|
||||
if (prev_dict != NULL) {
|
||||
// decref for the dictionary that we replaced
|
||||
decref_maybe_delay((PyObject *)prev_dict, !clear);
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
PyDictObject *dict = _PyObject_GetManagedDict(obj);
|
||||
if (dict == NULL) {
|
||||
#ifdef Py_GIL_DISABLED
|
||||
Py_BEGIN_CRITICAL_SECTION(obj);
|
||||
|
||||
dict = _PyObject_ManagedDictPointer(obj)->dict;
|
||||
if (dict == NULL) {
|
||||
set_dict_inline_values(obj, (PyDictObject *)new_dict);
|
||||
}
|
||||
|
||||
Py_END_CRITICAL_SECTION();
|
||||
|
||||
if (dict == NULL) {
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
set_dict_inline_values(obj, (PyDictObject *)new_dict);
|
||||
return 0;
|
||||
}
|
||||
if (_PyDict_DetachFromObject(dict, obj) == 0) {
|
||||
_PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)Py_XNewRef(new_dict);
|
||||
Py_DECREF(dict);
|
||||
return 0;
|
||||
}
|
||||
assert(new_dict == NULL);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
Py_BEGIN_CRITICAL_SECTION2(dict, obj);
|
||||
|
||||
// We've locked dict, but the actual dict could have changed
|
||||
// since we locked it.
|
||||
dict = _PyObject_ManagedDictPointer(obj)->dict;
|
||||
err = _PyDict_DetachFromObject(dict, obj);
|
||||
assert(err == 0 || new_dict == NULL);
|
||||
if (err == 0) {
|
||||
FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
|
||||
(PyDictObject *)Py_XNewRef(new_dict));
|
||||
}
|
||||
Py_END_CRITICAL_SECTION2();
|
||||
|
||||
if (err == 0) {
|
||||
Py_XDECREF(dict);
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyDictObject *dict;
|
||||
|
|
@ -7144,17 +7239,22 @@ _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
|
|||
(PyDictObject *)Py_XNewRef(new_dict));
|
||||
|
||||
Py_END_CRITICAL_SECTION();
|
||||
|
||||
Py_XDECREF(dict);
|
||||
decref_maybe_delay((PyObject *)dict, !clear);
|
||||
}
|
||||
assert(_PyObject_InlineValuesConsistencyCheck(obj));
|
||||
return err;
|
||||
}
|
||||
|
||||
int
|
||||
_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
|
||||
{
|
||||
return set_or_clear_managed_dict(obj, new_dict, false);
|
||||
}
|
||||
|
||||
void
|
||||
PyObject_ClearManagedDict(PyObject *obj)
|
||||
{
|
||||
if (_PyObject_SetManagedDict(obj, NULL) < 0) {
|
||||
if (set_or_clear_managed_dict(obj, NULL, true) < 0) {
|
||||
/* Must be out of memory */
|
||||
assert(PyErr_Occurred() == PyExc_MemoryError);
|
||||
PyErr_WriteUnraisable(NULL);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue