]> granicus.if.org Git - python/commitdiff
prevent the dict constructor from accepting non-string keyword args #8419
authorBenjamin Peterson <benjamin@python.org>
Sat, 24 Apr 2010 18:21:17 +0000 (18:21 +0000)
committerBenjamin Peterson <benjamin@python.org>
Sat, 24 Apr 2010 18:21:17 +0000 (18:21 +0000)
This adds PyArg_ValidateKeywordArguments, which checks that keyword arguments
are all strings, using an optimized method if possible.

Doc/c-api/arg.rst
Include/dictobject.h
Include/modsupport.h
Lib/test/test_dict.py
Misc/NEWS
Objects/dictobject.c
Python/getargs.c

index fc4b941619197f26e8396d6c3791eac15c74e738..a4e5555473f63d18016b7b952f1454ed972be596 100644 (file)
@@ -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, ...)
 
index d8c409edab9eb141469a1a34c8f262a50e7e89a0..5623379d36299ab9d1a9dd0d0001830b660351bc 100644 (file)
@@ -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);
index 23e8fa66a747edaa06c598b1bd223af6586ee58e..57886df8905e3cd57d1c8412661e5510720bdd1f 100644 (file)
@@ -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 *, ...);
index 4ba0b715540c96c4ff0c31ba9da4eb684a36b919..6c5f6829bec55e99712aa9ecc57f81fe3ae44375 100644 (file)
@@ -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(), {})
index 44e7fa294e805fbb603fd451be6f1e009a114e5a..0d72c83815cc935f52012ab60b2bb8019eb38a58 100644 (file)
--- 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!).
index 1d36e1d4dc299ad5e5526d6b16951daa6baa965b..5433ff743ba717ac3b248e43b9c06f861af6aefd 100644 (file)
@@ -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;
 }
 
index 17c5317d34dc702b3b5d837d51d06b161bb93b27..69f5018c4cc23d13f09aaf3d89b3013970bd92a4 100644 (file)
@@ -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