bpo-36907: fix refcount bug in _PyStack_UnpackDict() (GH-13381) · python/cpython@77aa396

3 files changed

lines changed

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()

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.

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

}