Skip to content

Commit 3959ee4

Browse files
gh-155733: validate keyword keys in operator.methodcaller and functools.partial
1 parent 6f8f97f commit 3959ee4

4 files changed

Lines changed: 42 additions & 3 deletions

File tree

Lib/test/test_functools.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ def test_keyword(self):
160160
empty, got = p(x=None)
161161
self.assertTrue(expected == got and empty == ())
162162

163+
def test_non_string_keywords(self):
164+
with self.assertRaisesRegex(TypeError, "keywords must be strings"):
165+
self.partial(capture, **{1: 'x'})
166+
p = self.partial(capture)
167+
p.keywords[1] = 'x'
168+
with self.assertRaisesRegex(TypeError, "keywords must be strings"):
169+
p()
170+
163171
def test_no_side_effects(self):
164172
# make sure there are no side effects that affect subsequent calls
165173
p = self.partial(capture, 0, a=1)

Lib/test/test_operator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,11 @@ def return_arguments(self, *args, **kwds):
511511
f = operator.methodcaller('return_arguments', *many_positional_arguments, **many_kw_arguments)
512512
self.assertEqual(f(a), (many_positional_arguments, many_kw_arguments))
513513

514+
def test_non_string_keywords(self):
515+
operator = self.module
516+
with self.assertRaisesRegex(TypeError, "keywords must be strings"):
517+
operator.methodcaller('x', **{1: 'x'})
518+
514519
def test_inplace(self):
515520
operator = self.module
516521
class C(object):

Modules/_functoolsmodule.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,16 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
190190
return NULL;
191191
}
192192

193-
/* keyword Placeholder prohibition */
193+
/* keyword Placeholder prohibition and key type validation */
194194
if (kw != NULL) {
195195
PyObject *key, *val;
196196
Py_ssize_t pos = 0;
197197
while (PyDict_Next(kw, &pos, &key, &val)) {
198+
if (!PyUnicode_Check(key)) {
199+
PyErr_SetString(PyExc_TypeError,
200+
"keywords must be strings");
201+
return NULL;
202+
}
198203
if (val == phold) {
199204
PyErr_SetString(PyExc_TypeError,
200205
"Placeholder cannot be passed as a keyword argument");
@@ -493,18 +498,27 @@ partial_vectorcall(PyObject *self, PyObject *const *args,
493498
PyTuple_SET_ITEM(tot_kwnames, pto_nkwds + i, key);
494499
}
495500

496-
/* Copy pto_keywords with overlapping call keywords merged
497-
* Note, tail is already coppied. */
498501
Py_ssize_t pos = 0, i = 0;
499502
PyObject *keyword_dict = n_merges ? pto_kw_merged : pto->kw;
503+
int valid_keys = 1;
500504
Py_BEGIN_CRITICAL_SECTION(keyword_dict);
501505
while (PyDict_Next(keyword_dict, &pos, &key, &val)) {
506+
if (!PyUnicode_Check(key)) {
507+
valid_keys = 0;
508+
break;
509+
}
502510
assert(i < pto_nkwds);
503511
PyTuple_SET_ITEM(tot_kwnames, i, Py_NewRef(key));
504512
stack[tot_nargs + i] = val;
505513
i++;
506514
}
507515
Py_END_CRITICAL_SECTION();
516+
if (!valid_keys) {
517+
PyErr_SetString(PyExc_TypeError, "keywords must be strings");
518+
Py_XDECREF(pto_kw_merged);
519+
Py_DECREF(tot_kwnames);
520+
goto error;
521+
}
508522
assert(i == pto_nkwds);
509523
Py_XDECREF(pto_kw_merged);
510524

Modules/_operator.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,18 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17051705
return NULL;
17061706
}
17071707

1708+
if (kwds != NULL && PyDict_Check(kwds)) {
1709+
PyObject *key, *value;
1710+
Py_ssize_t pos = 0;
1711+
while (PyDict_Next(kwds, &pos, &key, &value)) {
1712+
if (!PyUnicode_Check(key)) {
1713+
PyErr_SetString(PyExc_TypeError,
1714+
"keywords must be strings");
1715+
return NULL;
1716+
}
1717+
}
1718+
}
1719+
17081720
_operator_state *state = _PyType_GetModuleState(type);
17091721
/* create methodcallerobject structure */
17101722
mc = PyObject_GC_New(methodcallerobject, (PyTypeObject *)state->methodcaller_type);

0 commit comments

Comments
 (0)