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 *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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy