diff options
author | Jeroen Demeyer <J.Demeyer@UGent.be> | 2019-05-22 14:52:13 +0200 |
---|---|---|
committer | Petr Viktorin <pviktori@redhat.com> | 2019-05-22 14:52:13 +0200 |
commit | d092caf096fa48baadfc0900792206bb5aa0192d (patch) | |
tree | abf22ad34897b4715b6f80d511aff99bf1ea9375 | |
parent | 791e5fcbab9e444b62d13d08707cbbbeb9406297 (diff) | |
download | cpython-git-d092caf096fa48baadfc0900792206bb5aa0192d.tar.gz |
bpo-36907: fix refcount bug in _PyStack_UnpackDict() (GH-13381) (GH-13493)
-rw-r--r-- | Lib/test/test_call.py | 17 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2019-05-17-12-28-24.bpo-36907.rk7kgp.rst | 2 | ||||
-rw-r--r-- | Objects/call.c | 17 |
3 files changed, 31 insertions, 5 deletions
diff --git a/Lib/test/test_call.py b/Lib/test/test_call.py index 362c31c40c..1e6740244b 100644 --- a/Lib/test/test_call.py +++ b/Lib/test/test_call.py @@ -8,6 +8,7 @@ except ImportError: import struct import collections import itertools +import gc class FunctionCalls(unittest.TestCase): @@ -438,6 +439,22 @@ class FastCallTests(unittest.TestCase): result = _testcapi.pyobject_fastcallkeywords(func, args, kwnames) self.check_result(result, expected) + def test_fastcall_clearing_dict(self): + # Test bpo-36907: the point of the test is just checking that this + # does not crash. + class IntWithDict: + __slots__ = ["kwargs"] + def __init__(self, **kwargs): + self.kwargs = kwargs + def __int__(self): + self.kwargs.clear() + gc.collect() + return 0 + x = IntWithDict(dont_inherit=IntWithDict()) + # We test the argument handling of "compile" here, the compilation + # itself is not relevant. When we pass flags=x below, x.__int__() is + # called, which changes the keywords dict. + compile("pass", "", "exec", x, **x.kwargs) if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-05-17-12-28-24.bpo-36907.rk7kgp.rst b/Misc/NEWS.d/next/Core and Builtins/2019-05-17-12-28-24.bpo-36907.rk7kgp.rst new file mode 100644 index 0000000000..ae502e83ef --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-05-17-12-28-24.bpo-36907.rk7kgp.rst @@ -0,0 +1,2 @@ +Fix a crash when calling a C function with a keyword dict (``f(**kwargs)``) +and changing the dict ``kwargs`` while that function is running. diff --git a/Objects/call.c b/Objects/call.c index e6076e7005..1209ed3977 100644 --- a/Objects/call.c +++ b/Objects/call.c @@ -542,10 +542,14 @@ _PyMethodDef_RawFastCallDict(PyMethodDef *method, PyObject *self, } result = (*fastmeth) (self, stack, nargs, kwnames); - if (stack != args) { + if (kwnames != NULL) { + Py_ssize_t i, n = nargs + PyTuple_GET_SIZE(kwnames); + for (i = 0; i < n; i++) { + Py_DECREF(stack[i]); + } PyMem_Free((PyObject **)stack); + Py_DECREF(kwnames); } - Py_XDECREF(kwnames); break; } @@ -1379,8 +1383,11 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs, return -1; } - /* Copy position arguments (borrowed references) */ - memcpy(stack, args, nargs * sizeof(stack[0])); + /* Copy positional arguments */ + for (i = 0; i < nargs; i++) { + Py_INCREF(args[i]); + stack[i] = args[i]; + } kwstack = stack + nargs; pos = i = 0; @@ -1389,8 +1396,8 @@ _PyStack_UnpackDict(PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs, called in the performance critical hot code. */ while (PyDict_Next(kwargs, &pos, &key, &value)) { Py_INCREF(key); + Py_INCREF(value); PyTuple_SET_ITEM(kwnames, i, key); - /* The stack contains borrowed references */ kwstack[i] = value; i++; } |