From fb88636199c12f63d6c8c89f311cdafc91f30d2f Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sat, 24 Apr 2010 18:21:17 +0000 Subject: prevent the dict constructor from accepting non-string keyword args #8419 This adds PyArg_ValidateKeywordArguments, which checks that keyword arguments are all strings, using an optimized method if possible. --- Doc/c-api/arg.rst | 7 +++++++ Include/dictobject.h | 1 + Include/modsupport.h | 1 + Lib/test/test_dict.py | 6 ++++++ Misc/NEWS | 6 ++++++ Objects/dictobject.c | 23 +++++++++++++++++++++-- Python/getargs.c | 15 +++++++++++++++ 7 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/arg.rst b/Doc/c-api/arg.rst index fc4b941..a4e5555 100644 --- a/Doc/c-api/arg.rst +++ b/Doc/c-api/arg.rst @@ -366,6 +366,13 @@ and the following format units are left untouched. 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, ...) diff --git a/Include/dictobject.h b/Include/dictobject.h index d8c409e..5623379 100644 --- a/Include/dictobject.h +++ b/Include/dictobject.h @@ -126,6 +126,7 @@ PyAPI_FUNC(int) PyDict_Contains(PyObject *mp, PyObject *key); 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_HasOnlyStringKeys(PyObject *mp); /* PyDict_Update(mp, other) is equivalent to PyDict_Merge(mp, other, 1). */ PyAPI_FUNC(int) PyDict_Update(PyObject *mp, PyObject *other); diff --git a/Include/modsupport.h b/Include/modsupport.h index 23e8fa6..57886df 100644 --- a/Include/modsupport.h +++ b/Include/modsupport.h @@ -27,6 +27,7 @@ PyAPI_FUNC(int) PyArg_Parse(PyObject *, const char *, ...); 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 *, ...); diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 4ba0b71..6c5f682 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -7,6 +7,12 @@ import gc, weakref 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(), {}) diff --git a/Misc/NEWS b/Misc/NEWS index 44e7fa2..0d72c83 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 3.2 Alpha 1? Core and Builtins ----------------- +- Issue #8419: Prevent the dict constructor from accepting non-string keyword + arguments. + - Issue #8124: PySys_WriteStdout() and PySys_WriteStderr() don't execute indirectly Python signal handlers anymore because mywrite() ignores exceptions (KeyboardInterrupt) @@ -282,6 +285,9 @@ Core and Builtins C-API ----- +- Add PyArg_ValidateKeywordArguments, which checks if all keyword arguments are + strings in an efficient manner. + - Issue #8276: PyEval_CallObject() is now only available in macro form. The function declaration, which was kept for backwards compatibility reasons, is now removed (the macro was introduced in 1997!). diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 1d36e1d..5433ff7 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -458,6 +458,21 @@ lookdict_unicode(PyDictObject *mp, PyObject *key, register long hash) return 0; } +int +_PyDict_HasOnlyStringKeys(PyObject *dict) +{ + Py_ssize_t pos = 0; + PyObject *key, *value; + assert(PyDict_CheckExact(dict)); + /* Shortcut */ + if (((PyDictObject *)dict)->ma_lookup == lookdict_unicode) + return 1; + while (PyDict_Next(dict, &pos, &key, &value)) + if (!PyUnicode_Check(key)) + return 0; + return 1; +} + #ifdef SHOW_TRACK_COUNT #define INCREASE_TRACK_COUNT \ (count_tracked++, count_untracked--); @@ -1386,8 +1401,12 @@ dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, char *methnam 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; } diff --git a/Python/getargs.c b/Python/getargs.c index 17c5317..69f5018 100644 --- a/Python/getargs.c +++ b/Python/getargs.c @@ -1607,6 +1607,21 @@ _PyArg_VaParseTupleAndKeywords_SizeT(PyObject *args, return retval; } +int +PyArg_ValidateKeywordArguments(PyObject *kwargs) +{ + if (!PyDict_CheckExact(kwargs)) { + PyErr_BadInternalCall(); + return 0; + } + if (!_PyDict_HasOnlyStringKeys(kwargs)) { + 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 -- cgit v0.12