Index: Python/getargs.c =================================================================== --- Python/getargs.c (revision 80121) +++ Python/getargs.c (working copy) @@ -1607,6 +1607,28 @@ return retval; } +int +PyArg_ValidateKeywordArguments(PyObject *kwargs) +{ + Py_ssize_t pos = 0; + PyObject *key, *value; + if (!PyDict_CheckExact(kwargs)) { + PyErr_BadInternalCall(); + return 0; + } + /* Check if we can bypass looking through the whole dict. */ + if (!_PyDict_Optimized(kwargs)) { + while (PyDict_Next(kwargs, &pos, &key, &value)) { + if (!(PyUnicode_Check(key))) { + PyErr_SetString(PyExc_TypeError, + "keyword arguments must be strings"); + return 0; + } + } + } + return 1; +} + #define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':') static int Index: Include/dictobject.h =================================================================== --- Include/dictobject.h (revision 80121) +++ Include/dictobject.h (working copy) @@ -126,6 +126,7 @@ PyAPI_FUNC(int) _PyDict_Contains(PyObject *mp, PyObject *key, long hash); PyAPI_FUNC(PyObject *) _PyDict_NewPresized(Py_ssize_t minused); PyAPI_FUNC(void) _PyDict_MaybeUntrack(PyObject *mp); +PyAPI_FUNC(int) _PyDict_Optimized(PyObject *mp); /* PyDict_Update(mp, other) is equivalent to PyDict_Merge(mp, other, 1). */ PyAPI_FUNC(int) PyDict_Update(PyObject *mp, PyObject *other); Index: Include/modsupport.h =================================================================== --- Include/modsupport.h (revision 80121) +++ Include/modsupport.h (working copy) @@ -27,6 +27,7 @@ PyAPI_FUNC(int) PyArg_ParseTuple(PyObject *, const char *, ...) Py_FORMAT_PARSETUPLE(PyArg_ParseTuple, 2, 3); PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords(PyObject *, PyObject *, const char *, char **, ...); +PyAPI_FUNC(int) PyArg_ValidateKeywordArguments(PyObject *); PyAPI_FUNC(int) PyArg_UnpackTuple(PyObject *, const char *, Py_ssize_t, Py_ssize_t, ...); PyAPI_FUNC(PyObject *) Py_BuildValue(const char *, ...); PyAPI_FUNC(PyObject *) _Py_BuildValue_SizeT(const char *, ...); Index: Objects/dictobject.c =================================================================== --- Objects/dictobject.c (revision 80121) +++ Objects/dictobject.c (working copy) @@ -458,6 +458,13 @@ return 0; } +int +_PyDict_Optimized(PyObject *dict) +{ + assert(PyDict_CheckExact(dict)); + return ((PyDictObject *)dict)->ma_lookup == lookdict_unicode; +} + #ifdef SHOW_TRACK_COUNT #define INCREASE_TRACK_COUNT \ (count_tracked++, count_untracked--); @@ -1386,8 +1393,12 @@ else result = PyDict_MergeFromSeq2(self, arg, 1); } - if (result == 0 && kwds != NULL) - result = PyDict_Merge(self, kwds, 1); + if (result == 0 && kwds != NULL) { + if (PyArg_ValidateKeywordArguments(kwds)) + result = PyDict_Merge(self, kwds, 1); + else + result = -1; + } return result; } Index: Doc/c-api/arg.rst =================================================================== --- Doc/c-api/arg.rst (revision 80121) +++ Doc/c-api/arg.rst (working copy) @@ -366,6 +366,13 @@ va_list rather than a variable number of arguments. +.. cfunction:: int PyArg_ValidateKeywordArguments(PyObject *) + + Ensure that the keys in the keywords argument dictionary are strings. This + is only needed if :cfunc:`PyArg_ParseTupleAndKeywords` is not used, since the + latter already does this check. + + .. XXX deprecated, will be removed .. cfunction:: int PyArg_Parse(PyObject *args, const char *format, ...) Index: Lib/test/test_dict.py =================================================================== --- Lib/test/test_dict.py (revision 80121) +++ Lib/test/test_dict.py (working copy) @@ -7,6 +7,12 @@ class DictTest(unittest.TestCase): + def test_invalid_keyword_arguments(self): + with self.assertRaises(TypeError): + dict(**{1 : 2}) + with self.assertRaises(TypeError): + {}.update(**{1 : 2}) + def test_constructor(self): # calling built-in types without argument must return empty self.assertEqual(dict(), {})