8000 [2.7] bpo-23927: Make getargs.c skipitem() skipping 'w*'. (GH-8192). … · python/cpython@ef19fd2 · GitHub
[go: up one dir, main page]

Skip to content

Commit ef19fd2

Browse files
[2.7] bpo-23927: Make getargs.c skipitem() skipping 'w*'. (GH-8192). (GH-8255)
(cherry picked from commit 504373c) Also backport tests for skipitem() and handling errors.
1 parent 6f036bb commit ef19fd2

File tree

4 files changed

+194
-2
lines changed

4 files changed

+194
-2
lines changed

Lib/test/test_capi.py

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# these are all functions _testcapi exports whose name begins with 'test_'.
33

44
from __future__ import with_statement
5+
import string
56
import sys
67
import time
78
import random
@@ -101,6 +102,133 @@ def test_pendingcalls_non_threaded(self):
101102
self.pendingcalls_wait(l, n)
102103

103104

105+
# Bug #6012
106+
class Test6012(unittest.TestCase):
107+
def test(self):
108+
self.assertEqual(_testcapi.argparsing("Hello", "World"), 1)
109+
110+
111+
class SkipitemTest(unittest.TestCase):
112+
113+
def test_skipitem(self):
114+
"""
115+
If this test failed, you probably added a new "format unit"
116+
in Python/getargs.c, but neglected to update our poor friend
117+
skipitem() in the same file. (If so, shame on you!)
118+
119+
With a few exceptions**, this function brute-force tests all
120+
printable ASCII*** characters (32 to 126 inclusive) as format units,
121+
checking to see that PyArg_ParseTupleAndKeywords() return consistent
122+
errors both when the unit is attempted to be used and when it is
123+
skipped. If the format unit doesn't exist, we'll get one of two
124+
specific error messages (one for used, one for skipped); if it does
125+
exist we *won't* get that error--we'll get either no error or some
126+
other error. If we get the specific "does not exist" error for one
127+
test and not for the other, there's a mismatch, and the test fails.
128+
129+
** Some format units have special funny semantics and it would
130+
be difficult to accommodate them here. Since these are all
131+
well-established and properly skipped in skipitem() we can
132+
get away with not testing them--this test is really intended
133+
to catch *new* format units.
134+
135+
*** Python C source files must be ASCII. Therefore it's impossible
136+
to have non-ASCII format units.
137+
138+
"""
139+
empty_tuple = ()
140+
tuple_1 = (0,)
141+
dict_b = {'b':1}
142+
keywords = ["a", "b"]
143+
144+
for i in range(32, 127):
145+
c = chr(i)
146+
147+
# skip parentheses, the error reporting is inconsistent about them
148+
# skip 'e', it's always a two-character code
149+
# skip '|', it doesn't represent arguments anyway
150+
if c in '()e|':
151+
continue
152+
153+
# test the format unit when not skipped
154+
format = c + "i"
155+
try:
156+
_testcapi.parse_tuple_and_keywords(tuple_1, dict_b,
157+
format, keywords)
158+
when_not_skipped = False
159+
except TypeError as e:
160+
s = "argument 1 (impossible<bad format char>)"
161+
when_not_skipped = (str(e) == s)
162+
except RuntimeError:
163+
when_not_skipped = False
164+
165+
# test the format unit when skipped
166+
optional_format = "|" + format
167+
try:
168+
_testcapi.parse_tuple_and_keywords(empty_tuple, dict_b,
169+
optional_format, keywords)
170+
when_skipped = False
171+
except RuntimeError as e:
172+
s = "impossible<bad format char>: '{}'".format(format)
173+
when_skipped = (str(e) == s)
174+
175+
message = ("test_skipitem_parity: "
176+
"detected mismatch between convertsimple and skipitem "
177+
"for format unit '{}' ({}), not skipped {}, skipped {}".format(
178+
c, i, when_skipped, when_not_skipped))
179+
self.assertIs(when_skipped, when_not_skipped, message)
180+
181+
def test_skipitem_with_suffix(self):
182+
parse = _testcapi.parse_tuple_and_keywords
183+
empty_tuple = ()
184+
tuple_1 = (0,)
185+
dict_b = {'b':1}
186+
keywords = ["a", "b"]
187+
188+
supported = ('s#', 's*', 'z#', 'z*', 'u#', 't#', 'w#', 'w*')
189+
for c in string.ascii_letters:
190+
for c2 in '#*':
191+
f = c + c2
192+
optional_format = "|" + f + "i"
193+
if f in supported:
194+
parse(empty_tuple, dict_b, optional_format, keywords)
195+
else:
196+
with self.assertRaisesRegexp((RuntimeError, TypeError),
197+
'impossible<bad format char>'):
198+
parse(empty_tuple, dict_b, optional_format, keywords)
199+
200+
for c in map(chr, range(32, 128)):
201+
f = 'e' + c
202+
optional_format = "|" + f + "i"
203+
if c in 'st':
204+
parse(empty_tuple, dict_b, optional_format, keywords)
205+
else:
206+
with self.assertRaisesRegexp(RuntimeError,
207+
'impossible<bad format char>'):
208+
parse(empty_tuple, dict_b, optional_format, keywords)
209+
210+
def test_parse_tuple_and_keywords(self):
211+
# Test handling errors in the parse_tuple_and_keywords helper itself
212+
self.assertRaises(TypeError, _testcapi.parse_tuple_and_keywords,
213+
(), {}, 42, [])
214+
self.assertRaises(ValueError, _testcapi.parse_tuple_and_keywords,
215+
(), {}, '', 42)
216+
self.assertRaises(ValueError, _testcapi.parse_tuple_and_keywords,
217+
(), {}, '', [''] * 42)
218+
self.assertRaises(TypeError, _testcapi.parse_tuple_and_keywords,
219+
(), {}, '', [42])
220+
221+
def test_bad_use(self):
222+
# Test handling invalid format and keywords in
223+
# PyArg_ParseTupleAndKeywords()
224+
self.assertRaises(TypeError, _testcapi.parse_tuple_and_keywords,
225+
(1,), {}, '||O', ['a'])
226+
self.assertRaises(RuntimeError, _testcapi.parse_tuple_and_keywords,
227+
(1,), {}, '|O', ['a', 'b'])
228+
self.assertRaises(RuntimeError, _testcapi.parse_tuple_and_keywords,
229+
(1,), {}, '|OO', ['a'])
230+
231+
104232
@unittest.skipUnless(threading and thread, 'Threading required for this test.')
105233
class TestThreadState(unittest.TestCase):
106234

@@ -137,7 +265,8 @@ def test_main():
137265
except _testcapi.error:
138266
raise support.TestFailed, sys.exc_info()[1]
139267

140-
support.run_unittest(CAPITest, TestPendingCalls, TestThreadState)
268+
support.run_unittest(CAPITest, TestPendingCalls, SkipitemTest,
269+
TestThreadState)
141270

142271
if __name__ == "__main__":
143272
test_main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed :exc:`SystemError` in :c:func:`PyArg_ParseTupleAndKeywords` when the
2+
``w*`` format unit is used for optional parameter.

Modules/_testcapimodule.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,66 @@ getargs_et_hash(PyObject *self, PyObject *args)
15591559
return result;
15601560
}
15611561

1562+
static PyObject *
1563+
parse_tuple_and_keywords(PyObject *self, PyObject 566 *args)
1564+
{
1565+
PyObject *sub_args;
1566+
PyObject *sub_kwargs;
1567+
const char *sub_format;
1568+
PyObject *sub_keywords;
1569+
1570+
Py_ssize_t i, size;
1571+
char *keywords[8 + 1]; /* space for NULL at end */
1572+
PyObject *o;
1573+
1574+
int result;
1575+
PyObject *return_value = NULL;
1576+
1577+
double buffers[8][4]; /* double ensures alignment where necessary */
1578+
1579+
if (!PyArg_ParseTuple(args, "OOsO:parse_tuple_and_keywords",
1580+
&sub_args, &sub_kwargs,
1581+
&sub_format, &sub_keywords))
1582+
return NULL;
1583+
1584+
if (!(PyList_CheckExact(sub_keywords) || PyTuple_CheckExact(sub_keywords))) {
1585+
PyErr_SetString(PyExc_ValueError,
1586+
"parse_tuple_and_keywords: sub_keywords must be either list or tuple");
1587+
return NULL;
1588+
}
1589+
1590+
memset(buffers, 0, sizeof(buffers));
1591+
memset(keywords, 0, sizeof(keywords));
1592+
1593+
size = PySequence_Fast_GET_SIZE(sub_keywords);
1594+
if (size > 8) {
1595+
PyErr_SetString(PyExc_ValueError,
1596+
"parse_tuple_and_keywords: too many keywords in sub_keywords");
1597+
goto exit;
1598+
}
1599+
1600+
for (i = 0; i < size; i++) {
1601+
o = PySequence_Fast_GET_ITEM(sub_keywords, i);
1602+
keywords[i] = PyString_AsString(o);
1603+
if (keywords[i] == NULL) {
1604+
goto exit;
1605+
}
1606+
}
1607+
1608+
result = PyArg_ParseTupleAndKeywords(sub_args, sub_kwargs,
1609+
sub_format, keywords,
1610+
buffers + 0, buffers + 1, buffers + 2, buffers + 3,
1611+
buffers + 4, buffers + 5, buffers + 6, buffers + 7);
1612+
1613+
if (result) {
1614+
return_value = Py_None;
1615+
Py_INCREF(Py_None);
1616+
}
1617+
1618+
exit:
1619+
return return_value;
1620+
}
1621+
15621622
#ifdef Py_USING_UNICODE
15631623

15641624
static volatile int x;
@@ -2604,6 +2664,7 @@ static PyMethodDef TestMethods[] = {
26042664
#ifdef Py_USING_UNICODE
26052665
{"test_empty_argparse", (PyCFunction)test_empty_argparse,METH_NOARGS},
26062666
#endif
2667+
{"parse_tuple_and_keywords", parse_tuple_and_keywords, METH_VARARGS},
26072668
{"test_null_strings", (PyCFunction)test_null_strings, METH_NOARGS},
26082669
{"test_string_from_format", (PyCFunction)test_string_from_format, METH_NOARGS},
26092670
{"test_with_docstring", (PyCFunction)test_with_docstring, METH_NOARGS,

Python/getargs.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,7 @@ skipitem(const char **p_format, va_list *p_va, int flags)
17801780
else
17811781
(void) va_arg(*p_va, int *);
17821782
format++;
1783-
} else if ((c == 's' || c == 'z') && *format == '*') {
1783+
} else if ((c == 's' || c == 'z' || c == 'w') && *format == '*') {
17841784
format++;
17851785
}
17861786
break;

0 commit comments

Comments
 (0)
0