mirror of
https://github.com/python/cpython.git
synced 2026-02-14 03:10:44 +00:00
GH-125789: fix `fut._callbacks` to always return a copy of callbacks (#125922)
Fix `asyncio.Future._callbacks` to always return a copy of the internal list of callbacks to avoid mutation from user code affecting the internal state.
(cherry picked from commit cae853e3b4)
This commit is contained in:
parent
b67358125c
commit
f54e1a2d64
3 changed files with 44 additions and 28 deletions
|
|
@ -705,6 +705,24 @@ def test_future_iter_get_referents_segfault(self):
|
|||
evil = gc.get_referents(_asyncio)
|
||||
gc.collect()
|
||||
|
||||
def test_callbacks_copy(self):
|
||||
# See https://github.com/python/cpython/issues/125789
|
||||
# In C implementation, the `_callbacks` attribute
|
||||
# always returns a new list to avoid mutations of internal state
|
||||
|
||||
fut = self._new_future(loop=self.loop)
|
||||
f1 = lambda _: 1
|
||||
f2 = lambda _: 2
|
||||
fut.add_done_callback(f1)
|
||||
fut.add_done_callback(f2)
|
||||
callbacks = fut._callbacks
|
||||
self.assertIsNot(callbacks, fut._callbacks)
|
||||
fut.remove_done_callback(f1)
|
||||
callbacks = fut._callbacks
|
||||
self.assertIsNot(callbacks, fut._callbacks)
|
||||
fut.remove_done_callback(f2)
|
||||
self.assertIsNone(fut._callbacks)
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(futures, '_CFuture'),
|
||||
'requires the C _asyncio module')
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Fix possible crash when mutating list of callbacks returned by :attr:`!asyncio.Future._callbacks`. It now always returns a new copy in C implementation :mod:`!_asyncio`. Patch by Kumar Aditya.
|
||||
|
|
@ -1211,52 +1211,49 @@ static PyObject *
|
|||
FutureObj_get_callbacks(FutureObj *fut, void *Py_UNUSED(ignored))
|
||||
{
|
||||
asyncio_state *state = get_asyncio_state_by_def((PyObject *)fut);
|
||||
Py_ssize_t i;
|
||||
|
||||
ENSURE_FUTURE_ALIVE(state, fut)
|
||||
|
||||
if (fut->fut_callback0 == NULL) {
|
||||
if (fut->fut_callbacks == NULL) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
return Py_NewRef(fut->fut_callbacks);
|
||||
Py_ssize_t len = 0;
|
||||
if (fut->fut_callback0 != NULL) {
|
||||
len++;
|
||||
}
|
||||
|
||||
Py_ssize_t len = 1;
|
||||
if (fut->fut_callbacks != NULL) {
|
||||
len += PyList_GET_SIZE(fut->fut_callbacks);
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject *new_list = PyList_New(len);
|
||||
if (new_list == NULL) {
|
||||
PyObject *callbacks = PyList_New(len);
|
||||
if (callbacks == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *tup0 = PyTuple_New(2);
|
||||
if (tup0 == NULL) {
|
||||
Py_DECREF(new_list);
|
||||
return NULL;
|
||||
Py_ssize_t i = 0;
|
||||
if (fut->fut_callback0 != NULL) {
|
||||
PyObject *tup0 = PyTuple_New(2);
|
||||
if (tup0 == NULL) {
|
||||
Py_DECREF(callbacks);
|
||||
return NULL;
|
||||
}
|
||||
PyTuple_SET_ITEM(tup0, 0, Py_NewRef(fut->fut_callback0));
|
||||
assert(fut->fut_context0 != NULL);
|
||||
PyTuple_SET_ITEM(tup0, 1, Py_NewRef(fut->fut_context0));
|
||||
PyList_SET_ITEM(callbacks, i, tup0);
|
||||
i++;
|
||||
}
|
||||
|
||||
Py_INCREF(fut->fut_callback0);
|
||||
PyTuple_SET_ITEM(tup0, 0, fut->fut_callback0);
|
||||
assert(fut->fut_context0 != NULL);
|
||||
Py_INCREF(fut->fut_context0);
|
||||
PyTuple_SET_ITEM(tup0, 1, (PyObject *)fut->fut_context0);
|
||||
|
||||
PyList_SET_ITEM(new_list, 0, tup0);
|
||||
|
||||
if (fut->fut_callbacks != NULL) {
|
||||
for (i = 0; i < PyList_GET_SIZE(fut->fut_callbacks); i++) {
|
||||
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
|
||||
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(fut->fut_callbacks); j++) {
|
||||
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, j);
|
||||
Py_INCREF(cb);
|
||||
PyList_SET_ITEM(new_list, i + 1, cb);
|
||||
PyList_SET_ITEM(callbacks, i, cb);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return new_list;
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue