bpo-36907: fix refcount bug in _PyStack_UnpackDict() (GH-13381) (GH-1… · python/cpython@d092caf

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

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

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

@@ -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

}