This commit is contained in:
wangxiaolei 2026-05-04 00:29:49 +00:00 committed by GitHub
commit 003f5bc8fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 113 additions and 7 deletions

View file

@ -976,6 +976,93 @@ def test_weakref_list_is_not_traversed(self):
gc.collect()
def test_copy_concurrent_clear_in__getitem__(self):
# Prevent crashes when a copy mutates the OrderedDict.
# Regression tests for github.com/python/cpython/issues/142734.
class MyOD(self.OrderedDict):
def __getitem__(self, key):
super().clear()
return None
od = MyOD(enumerate(range(5)))
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)
def test_copy_concurrent_insertion_in__getitem__(self):
class MyOD(self.OrderedDict):
def __getitem__(self, key):
self['new_key'] = 'new_value'
return super().__getitem__(key)
od = MyOD([(1, 'one'), (2, 'two')])
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)
def test_copy_concurrent_deletion_by_del_in__getitem__(self):
class MyOD(self.OrderedDict):
call_count = 0
def __getitem__(self, key):
self.call_count += 1
if self.call_count == 1:
del self[3]
return super().__getitem__(key)
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three')])
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)
def test_copy_concurrent_deletion_by_pop_in__getitem__(self):
class MyOD(self.OrderedDict):
call_count = 0
def __getitem__(self, key):
self.call_count += 1
if self.call_count == 1:
self.pop(3)
return super().__getitem__(key)
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three')])
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)
def test_copy_concurrent_deletion_and_set_in__getitem__(self):
class MyOD(self.OrderedDict):
call_count = 0
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __getitem__(self, key):
self.call_count += 1
if self.call_count == 1:
del self[4]
elif self.call_count == 2:
self['new_key'] = 'new_value'
return super().__getitem__(key)
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')])
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)
def test_copy_concurrent_mutation_in__setitem__(self):
class MyOD(self.OrderedDict):
# When calling `__getitem__` on the source dict, `instance_count` is 1.
# When calling `__setitem__` on the target dict, `instance_count` is 2.
instance_count = 0
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
MyOD.instance_count += 1
def __setitem__(self, key, value):
if self.instance_count == 2 and len(od) > 1:
del od[next(iter(od))]
return super().__setitem__(key, value)
od = MyOD([(1, 'one'), (2, 'two'), (3, 'three')])
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)
self.assertEqual(MyOD.instance_count, 2)
class PurePythonOrderedDictSubclassTests(PurePythonOrderedDictTests):

View file

@ -0,0 +1 @@
:mod:`collections`: fix use-after-free crashes in :meth:`OrderedDict.copy <dict.copy>` when the dictionary to copy is concurrently mutated.

View file

@ -1265,21 +1265,39 @@ OrderedDict_copy_impl(PyObject *od)
}
}
else {
PyODictObject *self = _PyODictObject_CAST(od);
size_t state = self->od_state;
_odict_FOREACH(od, node) {
int res;
PyObject *value = PyObject_GetItem((PyObject *)od,
_odictnode_KEY(node));
if (value == NULL)
PyObject *key = Py_NewRef(_odictnode_KEY(node));
PyObject *value = PyObject_GetItem(od, key);
if (value == NULL) {
Py_DECREF(key);
goto fail;
res = PyObject_SetItem((PyObject *)od_copy,
_odictnode_KEY(node), value);
}
if (self->od_state != state) {
Py_DECREF(key);
Py_DECREF(value);
goto invalid_state; // 成功获取值但状态改变
}
int rc = PyObject_SetItem(od_copy, key, value);
Py_DECREF(key);
Py_DECREF(value);
if (res != 0)
if (rc != 0) {
goto fail;
}
if (self->od_state != state) {
goto invalid_state;
}
}
}
return od_copy;
invalid_state:
PyErr_SetString(PyExc_RuntimeError,
"OrderedDict mutated during iteration");
fail:
Py_DECREF(od_copy);
return NULL;