8000 GH-125789: fix `fut._callbacks` to always return a copy of callbacks … · python/cpython@cae853e · GitHub
[go: up one dir, main page]

Skip to content

Commit cae853e

Browse files
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.
1 parent ebcc578 commit cae853e

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
@@ -697,6 +697,24 @@ def test_future_del_segfault(self):
697697
with self.assertRaises(AttributeError):
698698
del fut._log_traceback
699699

700+
def test_callbacks_copy(self):
701+
# See https://github.com/python/cpython/issues/125789
702+
# In C implementation, the `_callbacks` attribute
703+
# always returns a new list to avoid mutations of internal state
704+
705+
fut = self._new_future(loop=self.loop)
706+
f1 = lambda _: 1
707+
f2 = lambda _: 2
708+
fut.add_done_callback(f1)
709+
fut.add_done_callback(f2)
710+
callbacks = fut._callbacks
711+
self.assertIsNot(callbacks, fut._callbacks)
712+
fut.remove_done_callback(f1)
713+
callbacks = fut._callbacks
714+
self.assertIsNot(callbacks, fut._callbacks)
715+
fut.remove_done_callback(f2)
716+
self.assertIsNone(fut._callbacks)
717+
700718

701719
@unittest.skipUnless(hasattr(futures, '_CFuture'),
702720
'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
@@ -1265,52 +1265,49 @@ static PyObject *
12651265
FutureObj_get_callbacks(FutureObj *fut, void *Py_UNUSED(ignored))
12661266
{
12671267
asyncio_state *state = get_asyncio_state_by_def((PyObject *)fut);
1268-
Py_ssize_t i;
1269-
12701268
ENSURE_FUTURE_ALIVE(state, fut)
12711269

1272-
if (fut->fut_callback0 == NULL) {
1273-
if (fut->fut_callbacks == NULL) {
1274-
Py_RETURN_NONE;
1275-
}
1276-
1277-
return Py_NewRef(fut->fut_callbacks);
1270+
Py_ssize_t len = 0;
1271+
if (fut->fut_callback0 != NULL) {
1272+
len++;
12781273
}
1279-
1280-
Py_ssize_t len = 1;
12811274
if (fut->fut_callbacks != NULL) {
12821275
len += PyList_GET_SIZE(fut->fut_callbacks);
12831276
}
12841277

1285-
1286-
PyObject *new_list = PyList_New(len);
1287-
if (new_list == NULL) {
1288-
return NULL;
1278+
if (len == 0) {
1279+
Py_RETURN_NONE;
12891280
}
12901281

1291-
PyObject *tup0 = PyTuple_New(2);
1292-
if (tup0 == NULL) {
1293-
Py_DECREF(new_list);
1282+
PyObject *callbacks = PyList_New(len);
1283+
if (callbacks == NULL) {
12941284
return NULL;
12951285
}
12961286

1297-
Py_INCREF(fut->fut_callback0);
1298-
PyTuple_SET_ITEM(tup0, 0, fut->fut_callback0);
1299-
assert(fut->fut_context0 != NULL);
1300-
Py_INCREF(fut->fut_context0);
1301-
PyTuple_SET_ITEM(tup0, 1, (PyObject *)fut->fut_context0);
1302-
1303-
PyList_SET_ITEM(new_list, 0, tup0);
1287+
Py_ssize_t i = 0;
1288+
if (fut->fut_callback0 != NULL) {
1289+
PyObject *tup0 = PyTuple_New(2);
1290+
if (tup0 == NULL) {
1291+
Py_DECREF(callbacks);
1292+
return NULL;
1293+
}
1294+
PyTuple_SET_ITEM(tup0, 0, Py_NewRef(fut->fut_callback0));
1295+
assert(fut->fut_context0 != NULL);
1296+
PyTuple_SET_ITEM(tup0, 1, Py_NewRef(fut->fut_context0));
1297+
PyList_SET_ITEM(callbacks, i, tup0);
1298+
i++;
1299+
}
13041300

13051301
if (fut->fut_callbacks != NULL) {
1306-
for (i = 0; i < PyList_GET_SIZE(fut->fut_callbacks); i++) {
1307-
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
1302+
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(fut->fut_callbacks); j++) {
1303+
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, j);
13081304
Py_INCREF(cb);
1309-
PyList_SET_ITEM(new_list, i + 1, cb);
1305+
PyList_SET_ITEM(callbacks, i, cb);
1306+
i++;
13101307
}
13111308
}
13121309

1313-
return new_list;
1310+
return callbacks;
13141311
}
13151312

13161313
static PyObject *

0 commit comments

Comments
 (0)
0