Skip to content

Commit f54e1a2

Browse files
[3.13] GH-125789: fix fut._callbacks to always return a copy of callbacks (#125922) (#125976)
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 cae853e)
1 parent b673581 commit f54e1a2

File tree

3 files changed

+44
-28
lines changed

3 files changed

+44
-28
lines changed

Lib/test/test_asyncio/test_futures.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,24 @@ def test_future_iter_get_referents_segfault(self):
705705
evil = gc.get_referents(_asyncio)
706706
gc.collect()
707707

708+
def test_callbacks_copy(self):
709+
# See https://github.com/python/cpython/issues/125789
710+
# In C implementation, the `_callbacks` attribute
711+
# always returns a new list to avoid mutations of internal state
712+
713+
fut = self._new_future(loop=self.loop)
714+
f1 = lambda _: 1
715+
f2 = lambda _: 2
716+
fut.add_done_callback(f1)
717+
fut.add_done_callback(f2)
718+
callbacks = fut._callbacks
719+
self.assertIsNot(callbacks, fut._callbacks)
720+
fut.remove_done_callback(f1)
721+
callbacks = fut._callbacks
722+
self.assertIsNot(callbacks, fut._callbacks)
723+
fut.remove_done_callback(f2)
724+
self.assertIsNone(fut._callbacks)
725+
708726

709727
@unittest.skipUnless(hasattr(futures, '_CFuture'),
710728
'requires the C _asyncio module')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

Modules/_asynciomodule.c

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,52 +1211,49 @@ static PyObject *
12111211
FutureObj_get_callbacks(FutureObj *fut, void *Py_UNUSED(ignored))
12121212
{
12131213
asyncio_state *state = get_asyncio_state_by_def((PyObject *)fut);
1214-
Py_ssize_t i;
1215-
12161214
ENSURE_FUTURE_ALIVE(state, fut)
12171215

1218-
if (fut->fut_callback0 == NULL) {
1219-
if (fut->fut_callbacks == NULL) {
1220-
Py_RETURN_NONE;
1221-
}
1222-
1223-
return Py_NewRef(fut->fut_callbacks);
1216+
Py_ssize_t len = 0;
1217+
if (fut->fut_callback0 != NULL) {
1218+
len++;
12241219
}
1225-
1226-
Py_ssize_t len = 1;
12271220
if (fut->fut_callbacks != NULL) {
12281221
len += PyList_GET_SIZE(fut->fut_callbacks);
12291222
}
12301223

1231-
1232-
PyObject *new_list = PyList_New(len);
1233-
if (new_list == NULL) {
1234-
return NULL;
1224+
if (len == 0) {
1225+
Py_RETURN_NONE;
12351226
}
12361227

1237-
PyObject *tup0 = PyTuple_New(2);
1238-
if (tup0 == NULL) {
1239-
Py_DECREF(new_list);
1228+
PyObject *callbacks = PyList_New(len);
1229+
if (callbacks == NULL) {
12401230
return NULL;
12411231
}
12421232

1243-
Py_INCREF(fut->fut_callback0);
1244-
PyTuple_SET_ITEM(tup0, 0, fut->fut_callback0);
1245-
assert(fut->fut_context0 != NULL);
1246-
Py_INCREF(fut->fut_context0);
1247-
PyTuple_SET_ITEM(tup0, 1, (PyObject *)fut->fut_context0);
1248-
1249-
PyList_SET_ITEM(new_list, 0, tup0);
1233+
Py_ssize_t i = 0;
1234+
if (fut->fut_callback0 != NULL) {
1235+
PyObject *tup0 = PyTuple_New(2);
1236+
if (tup0 == NULL) {
1237+
Py_DECREF(callbacks);
1238+
return NULL;
1239+
}
1240+
PyTuple_SET_ITEM(tup0, 0, Py_NewRef(fut->fut_callback0));
1241+
assert(fut->fut_context0 != NULL);
1242+
PyTuple_SET_ITEM(tup0, 1, Py_NewRef(fut->fut_context0));
1243+
PyList_SET_ITEM(callbacks, i, tup0);
1244+
i++;
1245+
}
12501246

12511247
if (fut->fut_callbacks != NULL) {
1252-
for (i = 0; i < PyList_GET_SIZE(fut->fut_callbacks); i++) {
1253-
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
1248+
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(fut->fut_callbacks); j++) {
1249+
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, j);
12541250
Py_INCREF(cb);
1255-
PyList_SET_ITEM(new_list, i + 1, cb);
1251+
PyList_SET_ITEM(callbacks, i, cb);
1252+
i++;
12561253
}
12571254
}
12581255

1259-
return new_list;
1256+
return callbacks;
12601257
}
12611258

12621259
static PyObject *

0 commit comments

Comments
 (0)