Skip to content

Commit 77aa396

Browse files
jdemeyerencukou
authored andcommitted
bpo-36907: fix refcount bug in _PyStack_UnpackDict() (GH-13381)
1 parent b892d3e commit 77aa396

File tree

3 files changed

+31
-5
lines changed

3 files changed

+31
-5
lines changed

Lib/test/test_call.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import struct
99
import collections
1010
import itertools
11+
import gc
1112

1213

1314
class FunctionCalls(unittest.TestCase):
@@ -457,6 +458,22 @@ def test_fastcall_keywords(self):
457458
result = _testcapi.pyobject_fastcallkeywords(func, args, kwnames)
458459
self.check_result(result, expected)
459460

461+
def test_fastcall_clearing_dict(self):
462+
# Test bpo-36907: the point of the test is just checking that this
463+
# does not crash.
464+
class IntWithDict:
465+
__slots__ = ["kwargs"]
466+
def __init__(self, **kwargs):
467+
self.kwargs = kwargs
468+
def __index__(self):
469+
self.kwargs.clear()
470+
gc.collect()
471+
return 0
472+
x = IntWithDict(dont_inherit=IntWithDict())
473+
# We test the argument handling of "compile" here, the compilation
474+
# itself is not relevant. When we pass flags=x below, x.__index__() is
475+
# called, which changes the keywords dict.
476+
compile("pass", "", "exec", x, **x.kwargs)
460477

461478
if __name__ == "__main__":
462479
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash when calling a C function with a keyword dict (``f(**kwargs)``)
2+
and changing the dict ``kwargs`` while that function is running.

Objects/call.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,10 +544,14 @@ _PyMethodDef_RawFastCallDict(PyMethodDef *method, PyObject *self,
544544
}
545545

546546
result = (*fastmeth) (self, stack, nargs, kwnames);
547-
if (stack != args) {
547+
if (kwnames != NULL) {
548+
Py_ssize_t i, n = nargs + PyTuple_GET_SIZE(kwnames);
549+
for (i = 0; i < n; i++) {
550+
Py_DECREF(stack[i]);
551+
}
548552
PyMem_Free((PyObject **)stack);
553+
Py_DECREF(kwnames);
549554
}
550-
Py_XDECREF(kwnames);
551555
break;
552556
}
553557

@@ -1334,8 +1338,11 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
13341338
return -1;
13351339
}
13361340

1337-
/* Copy position arguments (borrowed references) */
1338-
memcpy(stack, args, nargs * sizeof(stack[0]));
1341+
/* Copy positional arguments */
1342+
for (i = 0; i < nargs; i++) {
1343+
Py_INCREF(args[i]);
1344+
stack[i] = args[i];
1345+
}
13391346

13401347
kwstack = stack + nargs;
13411348
pos = i = 0;
@@ -1344,8 +1351,8 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
13441351
called in the performance critical hot code. */
13451352
while (PyDict_Next(kwargs, &pos, &key, &value)) {
13461353
Py_INCREF(key);
1354+
Py_INCREF(value);
13471355
PyTuple_SET_ITEM(kwnames, i, key);
1348-
/* The stack contains borrowed references */
13491356
kwstack[i] = value;
13501357
i++;
13511358
}

0 commit comments

Comments
 (0)