Skip to content

Commit d092caf

Browse files
jdemeyerencukou
authored andcommitted
bpo-36907: fix refcount bug in _PyStack_UnpackDict() (GH-13381) (GH-13493)
1 parent 791e5fc commit d092caf

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):
@@ -438,6 +439,22 @@ def test_fastcall_keywords(self):
438439
result = _testcapi.pyobject_fastcallkeywords(func, args, kwnames)
439440
self.check_result(result, expected)
440441

442+
def test_fastcall_clearing_dict(self):
443+
# Test bpo-36907: the point of the test is just checking that this
444+
# does not crash.
445+
class IntWithDict:
446+
__slots__ = ["kwargs"]
447+
def __init__(self, **kwargs):
448+
self.kwargs = kwargs
449+
def __int__(self):
450+
self.kwargs.clear()
451+
gc.collect()
452+
return 0
453+
x = IntWithDict(dont_inherit=IntWithDict())
454+
# We test the argument handling of "compile" here, the compilation
455+
# itself is not relevant. When we pass flags=x below, x.__int__() is
456+
# called, which changes the keywords dict.
457+
compile("pass", "", "exec", x, **x.kwargs)
441458

442459
if __name__ == "__main__":
443460
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
@@ -542,10 +542,14 @@ _PyMethodDef_RawFastCallDict(PyMethodDef *method, PyObject *self,
542542
}
543543

544544
result = (*fastmeth) (self, stack, nargs, kwnames);
545-
if (stack != args) {
545+
if (kwnames != NULL) {
546+
Py_ssize_t i, n = nargs + PyTuple_GET_SIZE(kwnames);
547+
for (i = 0; i < n; i++) {
548+
Py_DECREF(stack[i]);
549+
}
546550
PyMem_Free((PyObject **)stack);
551+
Py_DECREF(kwnames);
547552
}
548-
Py_XDECREF(kwnames);
549553
break;
550554
}
551555

@@ -1379,8 +1383,11 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
13791383
return -1;
13801384
}
13811385

1382-
/* Copy position arguments (borrowed references) */
1383-
memcpy(stack, args, nargs * sizeof(stack[0]));
1386+
/* Copy positional arguments */
1387+
for (i = 0; i < nargs; i++) {
1388+
Py_INCREF(args[i]);
1389+
stack[i] = args[i];
1390+
}
13841391

13851392
kwstack = stack + nargs;
13861393
pos = i = 0;
@@ -1389,8 +1396,8 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs,
13891396
called in the performance critical hot code. */
13901397
while (PyDict_Next(kwargs, &pos, &key, &value)) {
13911398
Py_INCREF(key);
1399+
Py_INCREF(value);
13921400
PyTuple_SET_ITEM(kwnames, i, key);
1393-
/* The stack contains borrowed references */
13941401
kwstack[i] = value;
13951402
i++;
13961403
}

0 commit comments

Comments
 (0)