Skip to content

Commit 1c44f88

Browse files
[3.12] gh-110815: Improve tests for PyArg_ParseTupleAndKeywords() (GH-110817) (GH-110825)
(cherry picked from commit 548ce09) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 1a7afa7 commit 1c44f88

File tree

2 files changed

+70
-18
lines changed

2 files changed

+70
-18
lines changed

Lib/test/test_capi/test_getargs.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
LLONG_MIN = -2**63
5656
ULLONG_MAX = 2**64-1
5757

58+
NULL = None
59+
5860
class Index:
5961
def __index__(self):
6062
return 99
@@ -1241,6 +1243,27 @@ def test_parse_tuple_and_keywords(self):
12411243
self.assertRaises(ValueError, _testcapi.parse_tuple_and_keywords,
12421244
(), {}, '', [42])
12431245

1246+
def test_basic(self):
1247+
parse = _testcapi.parse_tuple_and_keywords
1248+
1249+
self.assertEqual(parse((), {'a': 1}, 'O', ['a']), (1,))
1250+
self.assertEqual(parse((), {}, '|O', ['a']), (NULL,))
1251+
self.assertEqual(parse((1, 2), {}, 'OO', ['a', 'b']), (1, 2))
1252+
self.assertEqual(parse((1,), {'b': 2}, 'OO', ['a', 'b']), (1, 2))
1253+
self.assertEqual(parse((), {'a': 1, 'b': 2}, 'OO', ['a', 'b']), (1, 2))
1254+
self.assertEqual(parse((), {'b': 2}, '|OO', ['a', 'b']), (NULL, 2))
1255+
1256+
with self.assertRaisesRegex(TypeError,
1257+
"function missing required argument 'a'"):
1258+
parse((), {}, 'O', ['a'])
1259+
with self.assertRaisesRegex(TypeError,
1260+
"'b' is an invalid keyword argument"):
1261+
parse((), {'b': 1}, '|O', ['a'])
1262+
with self.assertRaisesRegex(TypeError,
1263+
fr"argument for function given by name \('a'\) "
1264+
fr"and position \(1\)"):
1265+
parse((1,), {'a': 2}, 'O|O', ['a', 'b'])
1266+
12441267
def test_bad_use(self):
12451268
# Test handling invalid format and keywords in
12461269
# PyArg_ParseTupleAndKeywords()
@@ -1268,20 +1291,23 @@ def test_bad_use(self):
12681291
def test_positional_only(self):
12691292
parse = _testcapi.parse_tuple_and_keywords
12701293

1271-
parse((1, 2, 3), {}, 'OOO', ['', '', 'a'])
1272-
parse((1, 2), {'a': 3}, 'OOO', ['', '', 'a'])
1294+
self.assertEqual(parse((1, 2, 3), {}, 'OOO', ['', '', 'a']), (1, 2, 3))
1295+
self.assertEqual(parse((1, 2), {'a': 3}, 'OOO', ['', '', 'a']), (1, 2, 3))
12731296
with self.assertRaisesRegex(TypeError,
12741297
r'function takes at least 2 positional arguments \(1 given\)'):
12751298
parse((1,), {'a': 3}, 'OOO', ['', '', 'a'])
1276-
parse((1,), {}, 'O|OO', ['', '', 'a'])
1299+
self.assertEqual(parse((1,), {}, 'O|OO', ['', '', 'a']),
1300+
(1, NULL, NULL))
12771301
with self.assertRaisesRegex(TypeError,
12781302
r'function takes at least 1 positional argument \(0 given\)'):
12791303
parse((), {}, 'O|OO', ['', '', 'a'])
1280-
parse((1, 2), {'a': 3}, 'OO$O', ['', '', 'a'])
1304+
self.assertEqual(parse((1, 2), {'a': 3}, 'OO$O', ['', '', 'a']),
1305+
(1, 2, 3))
12811306
with self.assertRaisesRegex(TypeError,
12821307
r'function takes exactly 2 positional arguments \(1 given\)'):
12831308
parse((1,), {'a': 3}, 'OO$O', ['', '', 'a'])
1284-
parse((1,), {}, 'O|O$O', ['', '', 'a'])
1309+
self.assertEqual(parse((1,), {}, 'O|O$O', ['', '', 'a']),
1310+
(1, NULL, NULL))
12851311
with self.assertRaisesRegex(TypeError,
12861312
r'function takes at least 1 positional argument \(0 given\)'):
12871313
parse((), {}, 'O|O$O', ['', '', 'a'])

Modules/_testcapi/getargs.c

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ parse_tuple_and_keywords(PyObject *self, PyObject *args)
1515
const char *sub_format;
1616
PyObject *sub_keywords;
1717

18-
double buffers[8][4]; /* double ensures alignment where necessary */
19-
PyObject *converted[8];
20-
char *keywords[8 + 1]; /* space for NULL at end */
18+
#define MAX_PARAMS 8
19+
double buffers[MAX_PARAMS][4]; /* double ensures alignment where necessary */
20+
char *keywords[MAX_PARAMS + 1]; /* space for NULL at end */
2121

2222
PyObject *return_value = NULL;
2323

@@ -37,41 +37,67 @@ parse_tuple_and_keywords(PyObject *self, PyObject *args)
3737
}
3838

3939
memset(buffers, 0, sizeof(buffers));
40-
memset(converted, 0, sizeof(converted));
4140
memset(keywords, 0, sizeof(keywords));
4241

4342
Py_ssize_t size = PySequence_Fast_GET_SIZE(sub_keywords);
44-
if (size > 8) {
43+
if (size > MAX_PARAMS) {
4544
PyErr_SetString(PyExc_ValueError,
4645
"parse_tuple_and_keywords: too many keywords in sub_keywords");
4746
goto exit;
4847
}
4948

5049
for (Py_ssize_t i = 0; i < size; i++) {
5150
PyObject *o = PySequence_Fast_GET_ITEM(sub_keywords, i);
52-
if (!PyUnicode_FSConverter(o, (void *)(converted + i))) {
51+
if (PyUnicode_Check(o)) {
52+
keywords[i] = (char *)PyUnicode_AsUTF8(o);
53+
if (keywords[i] == NULL) {
54+
goto exit;
55+
}
56+
}
57+
else if (PyBytes_Check(o)) {
58+
keywords[i] = PyBytes_AS_STRING(o);
59+
}
60+
else {
5361
PyErr_Format(PyExc_ValueError,
5462
"parse_tuple_and_keywords: "
55-
"could not convert keywords[%zd] to narrow string", i);
63+
"keywords must be str or bytes", i);
5664
goto exit;
5765
}
58-
keywords[i] = PyBytes_AS_STRING(converted[i]);
5966
}
6067

68+
assert(MAX_PARAMS == 8);
6169
int result = PyArg_ParseTupleAndKeywords(sub_args, sub_kwargs,
6270
sub_format, keywords,
6371
buffers + 0, buffers + 1, buffers + 2, buffers + 3,
6472
buffers + 4, buffers + 5, buffers + 6, buffers + 7);
6573

6674
if (result) {
67-
return_value = Py_NewRef(Py_None);
75+
int objects_only = 1;
76+
for (const char *f = sub_format; *f; f++) {
77+
if (Py_ISALNUM(*f) && strchr("OSUY", *f) == NULL) {
78+
objects_only = 0;
79+
break;
80+
}
81+
}
82+
if (objects_only) {
83+
return_value = PyTuple_New(size);
84+
if (return_value == NULL) {
85+
goto exit;
86+
}
87+
for (Py_ssize_t i = 0; i < size; i++) {
88+
PyObject *arg = *(PyObject **)(buffers + i);
89+
if (arg == NULL) {
90+
arg = Py_None;
91+
}
92+
PyTuple_SET_ITEM(return_value, i, Py_NewRef(arg));
93+
}
94+
}
95+
else {
96+
return_value = Py_NewRef(Py_None);
97+
}
6898
}
6999

70100
exit:
71-
size = sizeof(converted) / sizeof(converted[0]);
72-
for (Py_ssize_t i = 0; i < size; i++) {
73-
Py_XDECREF(converted[i]);
74-
}
75101
return return_value;
76102
}
77103

0 commit comments

Comments
 (0)