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

Skip to content

Commit 42927f7

Browse files
[3.12] GH-125789: fix fut._callbacks to always return a copy of callbacks (GH-125922) (#125977)
GH-125789: fix `fut._callbacks` to always return a copy of callbacks (GH-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. Co-authored-by: Kumar Aditya <kumaraditya@python.org> (cherry picked from commit cae853e)
1 parent 31ff9e5 commit 42927f7

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
@@ -686,6 +686,24 @@ def test_future_iter_get_referents_segfault(self):
686686
evil = gc.get_referents(_asyncio)
687687
gc.collect()
688688

689+
def test_callbacks_copy(self):
690+
# See https://github.com/python/cpython/issues/125789
691+
# In C implementation, the `_callbacks` attribute
692+
# always returns a new list to avoid mutations of internal state
693+
694+
fut = self._new_future(loop=self.loop)
695+
f1 = lambda _: 1
696+
f2 = lambda _: 2
697+
fut.add_done_callback(f1)
698+
fut.add_done_callback(f2)
699+
callbacks = fut._callbacks
700+
self.assertIsNot(callbacks, fut._callbacks)
701+
fut.remove_done_callback(f1)
702+
callbacks = fut._callbacks
703+
self.assertIsNot(callbacks, fut._callbacks)
704+
fut.remove_done_callback(f2)
705+
self.assertIsNone(fut._callbacks)
706+
689707

690708
@unittest.skipUnless(hasattr(futures, '_CFuture'),
691709
'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
@@ -1290,52 +1290,49 @@ static PyObject *
12901290
FutureObj_get_callbacks(FutureObj *fut, void *Py_UNUSED(ignored))
12911291
{
12921292
asyncio_state *state = get_asyncio_state_by_def((PyObject *)fut);
1293-
Py_ssize_t i;
1294-
12951293
ENSURE_FUTURE_ALIVE(state, fut)
12961294

1297-
if (fut->fut_callback0 == NULL) {
1298-
if (fut->fut_callbacks == NULL) {
1299-
Py_RETURN_NONE;
1300-
}
1301-
1302-
return Py_NewRef(fut->fut_callbacks);
1295+
Py_ssize_t len = 0;
1296+
if (fut->fut_callback0 != NULL) {
1297+
len++;
13031298
}
1304-
1305-
Py_ssize_t len = 1;
13061299
if (fut->fut_callbacks != NULL) {
13071300
len += PyList_GET_SIZE(fut->fut_callbacks);
13081301
}
13091302

1310-
1311-
PyObject *new_list = PyList_New(len);
1312-
if (new_list == NULL) {
1313-
return NULL;
1303+
if (len == 0) {
1304+
Py_RETURN_NONE;
13141305
}
13151306

1316-
PyObject *tup0 = PyTuple_New(2);
1317-
if (tup0 == NULL) {
1318-
Py_DECREF(new_list);
1307+
PyObject *callbacks = PyList_New(len);
1308+
if (callbacks == NULL) {
13191309
return NULL;
13201310
}
13211311

1322-
Py_INCREF(fut->fut_callback0);
1323-
PyTuple_SET_ITEM(tup0, 0, fut->fut_callback0);
1324-
assert(fut->fut_context0 != NULL);
1325-
Py_INCREF(fut->fut_context0);
1326-
PyTuple_SET_ITEM(tup0, 1, (PyObject *)fut->fut_context0);
1327-
1328-
PyList_SET_ITEM(new_list, 0, tup0);
1312+
Py_ssize_t i = 0;
1313+
if (fut->fut_callback0 != NULL) {
1314+
PyObject *tup0 = PyTuple_New(2);
1315+
if (tup0 == NULL) {
1316+
Py_DECREF(callbacks);
1317+
return NULL;
1318+
}
1319+
PyTuple_SET_ITEM(tup0, 0, Py_NewRef(fut->fut_callback0));
1320+
assert(fut->fut_context0 != NULL);
1321+
PyTuple_SET_ITEM(tup0, 1, Py_NewRef(fut->fut_context0));
1322+
PyList_SET_ITEM(callbacks, i, tup0);
1323+
i++;
1324+
}
13291325

13301326
if (fut->fut_callbacks != NULL) {
1331-
for (i = 0; i < PyList_GET_SIZE(fut->fut_callbacks); i++) {
1332-
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, i);
1327+
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(fut->fut_callbacks); j++) {
1328+
PyObject *cb = PyList_GET_ITEM(fut->fut_callbacks, j);
13331329
Py_INCREF(cb);
1334-
PyList_SET_ITEM(new_list, i + 1, cb);
1330+
PyList_SET_ITEM(callbacks, i, cb);
1331+
i++;
13351332
}
13361333
}
13371334

1338-
return new_list;
1335+
return callbacks;
13391336
}
13401337

13411338
static PyObject *

0 commit comments

Comments
 (0)
0