Skip to content

Commit fb88636

Browse files
committed
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.
1 parent b962171 commit fb88636

File tree

7 files changed

+57
-2
lines changed

7 files changed

+57
-2
lines changed

Doc/c-api/arg.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,13 @@ and the following format units are left untouched.
366366
va_list rather than a variable number of arguments.
367367

368368

369+
.. cfunction:: int PyArg_ValidateKeywordArguments(PyObject *)
370+
371+
Ensure that the keys in the keywords argument dictionary are strings. This
372+
is only needed if :cfunc:`PyArg_ParseTupleAndKeywords` is not used, since the
373+
latter already does this check.
374+
375+
369376
.. XXX deprecated, will be removed
370377
.. cfunction:: int PyArg_Parse(PyObject *args, const char *format, ...)
371378

Include/dictobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ PyAPI_FUNC(int) PyDict_Contains(PyObject *mp, PyObject *key);
126126
PyAPI_FUNC(int) _PyDict_Contains(PyObject *mp, PyObject *key, long hash);
127127
PyAPI_FUNC(PyObject *) _PyDict_NewPresized(Py_ssize_t minused);
128128
PyAPI_FUNC(void) _PyDict_MaybeUntrack(PyObject *mp);
129+
PyAPI_FUNC(int) _PyDict_HasOnlyStringKeys(PyObject *mp);
129130

130131
/* PyDict_Update(mp, other) is equivalent to PyDict_Merge(mp, other, 1). */
131132
PyAPI_FUNC(int) PyDict_Update(PyObject *mp, PyObject *other);

Include/modsupport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ PyAPI_FUNC(int) PyArg_Parse(PyObject *, const char *, ...);
2727
PyAPI_FUNC(int) PyArg_ParseTuple(PyObject *, const char *, ...) Py_FORMAT_PARSETUPLE(PyArg_ParseTuple, 2, 3);
2828
PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
2929
const char *, char **, ...);
30+
PyAPI_FUNC(int) PyArg_ValidateKeywordArguments(PyObject *);
3031
PyAPI_FUNC(int) PyArg_UnpackTuple(PyObject *, const char *, Py_ssize_t, Py_ssize_t, ...);
3132
PyAPI_FUNC(PyObject *) Py_BuildValue(const char *, ...);
3233
PyAPI_FUNC(PyObject *) _Py_BuildValue_SizeT(const char *, ...);

Lib/test/test_dict.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88
class DictTest(unittest.TestCase):
99

10+
def test_invalid_keyword_arguments(self):
11+
with self.assertRaises(TypeError):
12+
dict(**{1 : 2})
13+
with self.assertRaises(TypeError):
14+
{}.update(**{1 : 2})
15+
1016
def test_constructor(self):
1117
# calling built-in types without argument must return empty
1218
self.assertEqual(dict(), {})

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ What's New in Python 3.2 Alpha 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #8419: Prevent the dict constructor from accepting non-string keyword
16+
arguments.
17+
1518
- Issue #8124: PySys_WriteStdout() and PySys_WriteStderr() don't execute
1619
indirectly Python signal handlers anymore because mywrite() ignores
1720
exceptions (KeyboardInterrupt)
@@ -282,6 +285,9 @@ Core and Builtins
282285
C-API
283286
-----
284287

288+
- Add PyArg_ValidateKeywordArguments, which checks if all keyword arguments are
289+
strings in an efficient manner.
290+
285291
- Issue #8276: PyEval_CallObject() is now only available in macro form. The
286292
function declaration, which was kept for backwards compatibility reasons,
287293
is now removed (the macro was introduced in 1997!).

Objects/dictobject.c

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,21 @@ lookdict_unicode(PyDictObject *mp, PyObject *key, register long hash)
458458
return 0;
459459
}
460460

461+
int
462+
_PyDict_HasOnlyStringKeys(PyObject *dict)
463+
{
464+
Py_ssize_t pos = 0;
465+
PyObject *key, *value;
466+
assert(PyDict_CheckExact(dict));
467+
/* Shortcut */
468+
if (((PyDictObject *)dict)->ma_lookup == lookdict_unicode)
469+
return 1;
470+
while (PyDict_Next(dict, &pos, &key, &value))
471+
if (!PyUnicode_Check(key))
472+
return 0;
473+
return 1;
474+
}
475+
461476
#ifdef SHOW_TRACK_COUNT
462477
#define INCREASE_TRACK_COUNT \
463478
(count_tracked++, count_untracked--);
@@ -1386,8 +1401,12 @@ dict_update_common(PyObject *self, PyObject *args, PyObject *kwds, char *methnam
13861401
else
13871402
result = PyDict_MergeFromSeq2(self, arg, 1);
13881403
}
1389-
if (result == 0 && kwds != NULL)
1390-
result = PyDict_Merge(self, kwds, 1);
1404+
if (result == 0 && kwds != NULL) {
1405+
if (PyArg_ValidateKeywordArguments(kwds))
1406+
result = PyDict_Merge(self, kwds, 1);
1407+
else
1408+
result = -1;
1409+
}
13911410
return result;
13921411
}
13931412

Python/getargs.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,21 @@ _PyArg_VaParseTupleAndKeywords_SizeT(PyObject *args,
16071607
return retval;
16081608
}
16091609

1610+
int
1611+
PyArg_ValidateKeywordArguments(PyObject *kwargs)
1612+
{
1613+
if (!PyDict_CheckExact(kwargs)) {
1614+
PyErr_BadInternalCall();
1615+
return 0;
1616+
}
1617+
if (!_PyDict_HasOnlyStringKeys(kwargs)) {
1618+
PyErr_SetString(PyExc_TypeError,
1619+
"keyword arguments must be strings");
1620+
return 0;
1621+
}
1622+
return 1;
1623+
}
1624+
16101625
#define IS_END_OF_FORMAT(c) (c == '\0' || c == ';' || c == ':')
16111626

16121627
static int

0 commit comments

Comments
 (0)