Skip to content

Commit c047904

Browse files
committed
bpo-36907: fix refcount bug in _PyStack_UnpackDict()
1 parent bfba8c3 commit c047904

File tree

3 files changed

+29
-5
lines changed

3 files changed

+29
-5
lines changed

Lib/test/test_call.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,21 @@ def test_fastcall_keywords(self):
457457
result = _testcapi.pyobject_fastcallkeywords(func, args, kwnames)
458458
self.check_result(result, expected)
459459

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

461476
if __name__ == "__main__":
462477
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)