Mailing List Archive

gh-68114: Fix handling for removed PyArg_ParseTuple 'w' formatters (GH-8204)
https://github.com/python/cpython/commit/eb927e9fc823de9539fcb82c9ea9d055462eb04a
commit: eb927e9fc823de9539fcb82c9ea9d055462eb04a
branch: main
author: Joe Jevnik <JoeJev@gmail.com>
committer: encukou <encukou@gmail.com>
date: 2024-04-23T13:15:15+02:00
summary:

gh-68114: Fix handling for removed PyArg_ParseTuple 'w' formatters (GH-8204)

Co-authored-by: Joe Jevnik <joe@quantopian.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>

files:
A Misc/NEWS.d/next/C API/2024-03-18-17-29-52.gh-issue-68114.W7R_lI.rst
M Lib/test/test_capi/test_getargs.py
M Modules/_testcapi/getargs.c
M Python/getargs.c

diff --git a/Lib/test/test_capi/test_getargs.py b/Lib/test/test_capi/test_getargs.py
index 12039803ba543e..e710400f75c235 100644
--- a/Lib/test/test_capi/test_getargs.py
+++ b/Lib/test/test_capi/test_getargs.py
@@ -856,20 +856,24 @@ def test_y_hash(self):

def test_w_star(self):
# getargs_w_star() modifies first and last byte
- from _testcapi import getargs_w_star
- self.assertRaises(TypeError, getargs_w_star, 'abc\xe9')
- self.assertRaises(TypeError, getargs_w_star, b'bytes')
- self.assertRaises(TypeError, getargs_w_star, b'nul:\0')
- self.assertRaises(TypeError, getargs_w_star, memoryview(b'bytes'))
- buf = bytearray(b'bytearray')
- self.assertEqual(getargs_w_star(buf), b'[ytearra]')
- self.assertEqual(buf, bytearray(b'[ytearra]'))
- buf = bytearray(b'memoryview')
- self.assertEqual(getargs_w_star(memoryview(buf)), b'[emoryvie]')
- self.assertEqual(buf, bytearray(b'[emoryvie]'))
- self.assertRaises(TypeError, getargs_w_star, None)
- self.assertRaises(TypeError, getargs_w_star, NONCONTIG_WRITABLE)
- self.assertRaises(TypeError, getargs_w_star, NONCONTIG_READONLY)
+ # getargs_w_star_opt() takes additional optional args: with one
+ # argument it should behave the same as getargs_w_star
+ from _testcapi import getargs_w_star, getargs_w_star_opt
+ for func in (getargs_w_star, getargs_w_star_opt):
+ with self.subTest(func=func):
+ self.assertRaises(TypeError, func, 'abc\xe9')
+ self.assertRaises(TypeError, func, b'bytes')
+ self.assertRaises(TypeError, func, b'nul:\0')
+ self.assertRaises(TypeError, func, memoryview(b'bytes'))
+ buf = bytearray(b'bytearray')
+ self.assertEqual(func(buf), b'[ytearra]')
+ self.assertEqual(buf, bytearray(b'[ytearra]'))
+ buf = bytearray(b'memoryview')
+ self.assertEqual(func(memoryview(buf)), b'[emoryvie]')
+ self.assertEqual(buf, bytearray(b'[emoryvie]'))
+ self.assertRaises(TypeError, func, None)
+ self.assertRaises(TypeError, func, NONCONTIG_WRITABLE)
+ self.assertRaises(TypeError, func, NONCONTIG_READONLY)

def test_getargs_empty(self):
from _testcapi import getargs_empty
@@ -1112,9 +1116,9 @@ def test_skipitem(self):
c = chr(i)

# skip parentheses, the error reporting is inconsistent about them
- # skip 'e', it's always a two-character code
+ # skip 'e' and 'w', they're always two-character codes
# skip '|' and '$', they don't represent arguments anyway
- if c in '()e|$':
+ if c in '()ew|$':
continue

# test the format unit when not skipped
@@ -1152,7 +1156,7 @@ def test_skipitem_with_suffix(self):
dict_b = {'b':1}
keywords = ["a", "b"]

- supported = ('s#', 's*', 'z#', 'z*', 'y#', 'y*', 'w#', 'w*')
+ supported = ('s#', 's*', 'z#', 'z*', 'y#', 'y*', 'w*')
for c in string.ascii_letters:
for c2 in '#*':
f = c + c2
diff --git a/Misc/NEWS.d/next/C API/2024-03-18-17-29-52.gh-issue-68114.W7R_lI.rst b/Misc/NEWS.d/next/C API/2024-03-18-17-29-52.gh-issue-68114.W7R_lI.rst
new file mode 100644
index 00000000000000..fa09d2a0a72df7
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-03-18-17-29-52.gh-issue-68114.W7R_lI.rst
@@ -0,0 +1,2 @@
+Fixed skipitem()'s handling of the old 'w' and 'w#' formatters. These are
+no longer supported and now raise an exception if used.
diff --git a/Modules/_testcapi/getargs.c b/Modules/_testcapi/getargs.c
index 0d61d8c8969f82..ee04c760d27213 100644
--- a/Modules/_testcapi/getargs.c
+++ b/Modules/_testcapi/getargs.c
@@ -141,6 +141,122 @@ getargs_w_star(PyObject *self, PyObject *args)
return result;
}

+static PyObject *
+getargs_w_star_opt(PyObject *self, PyObject *args)
+{
+ Py_buffer buffer;
+ Py_buffer buf2;
+ int number = 1;
+
+ if (!PyArg_ParseTuple(args, "w*|w*i:getargs_w_star",
+ &buffer, &buf2, &number)) {
+ return NULL;
+ }
+
+ if (2 <= buffer.len) {
+ char *str = buffer.buf;
+ str[0] = '[';
+ str[buffer.len-1] = ']';
+ }
+
+ PyObject *result = PyBytes_FromStringAndSize(buffer.buf, buffer.len);
+ PyBuffer_Release(&buffer);
+ return result;
+}
+
+/* Test the old w and w# codes that no longer work */
+static PyObject *
+test_w_code_invalid(PyObject *self, PyObject *arg)
+{
+ static const char * const keywords[] = {"a", "b", "c", "d", NULL};
+ char *formats_3[] = {"O|w#$O",
+ "O|w$O",
+ "O|w#O",
+ "O|wO",
+ NULL};
+ char *formats_4[] = {"O|w#O$O",
+ "O|wO$O",
+ "O|Ow#O",
+ "O|OwO",
+ "O|Ow#$O",
+ "O|Ow$O",
+ NULL};
+ size_t n;
+ PyObject *args;
+ PyObject *kwargs;
+ PyObject *tmp;
+
+ if (!(args = PyTuple_Pack(1, Py_None))) {
+ return NULL;
+ }
+
+ kwargs = PyDict_New();
+ if (!kwargs) {
+ Py_DECREF(args);
+ return NULL;
+ }
+
+ if (PyDict_SetItemString(kwargs, "c", Py_None)) {
+ Py_DECREF(args);
+ Py_XDECREF(kwargs);
+ return NULL;
+ }
+
+ for (n = 0; formats_3[n]; ++n) {
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, formats_3[n],
+ (char**) keywords,
+ &tmp, &tmp, &tmp)) {
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ PyErr_Format(PyExc_AssertionError,
+ "test_w_code_invalid_suffix: %s",
+ formats_3[n]);
+ return NULL;
+ }
+ else {
+ if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ return NULL;
+ }
+ PyErr_Clear();
+ }
+ }
+
+ if (PyDict_DelItemString(kwargs, "c") ||
+ PyDict_SetItemString(kwargs, "d", Py_None)) {
+
+ Py_DECREF(kwargs);
+ Py_DECREF(args);
+ return NULL;
+ }
+
+ for (n = 0; formats_4[n]; ++n) {
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, formats_4[n],
+ (char**) keywords,
+ &tmp, &tmp, &tmp, &tmp)) {
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ PyErr_Format(PyExc_AssertionError,
+ "test_w_code_invalid_suffix: %s",
+ formats_4[n]);
+ return NULL;
+ }
+ else {
+ if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ return NULL;
+ }
+ PyErr_Clear();
+ }
+ }
+
+ Py_DECREF(args);
+ Py_DECREF(kwargs);
+ Py_RETURN_NONE;
+}
+
static PyObject *
getargs_empty(PyObject *self, PyObject *args, PyObject *kwargs)
{
@@ -684,6 +800,7 @@ static PyMethodDef test_methods[] = {
{"getargs_s_star", getargs_s_star, METH_VARARGS},
{"getargs_tuple", getargs_tuple, METH_VARARGS},
{"getargs_w_star", getargs_w_star, METH_VARARGS},
+ {"getargs_w_star_opt", getargs_w_star_opt, METH_VARARGS},
{"getargs_empty", _PyCFunction_CAST(getargs_empty), METH_VARARGS|METH_KEYWORDS},
{"getargs_y", getargs_y, METH_VARARGS},
{"getargs_y_hash", getargs_y_hash, METH_VARARGS},
@@ -693,6 +810,7 @@ static PyMethodDef test_methods[] = {
{"getargs_z_star", getargs_z_star, METH_VARARGS},
{"parse_tuple_and_keywords", parse_tuple_and_keywords, METH_VARARGS},
{"gh_99240_clear_args", gh_99240_clear_args, METH_VARARGS},
+ {"test_w_code_invalid", test_w_code_invalid, METH_NOARGS},
{NULL},
};

diff --git a/Python/getargs.c b/Python/getargs.c
index bec981698767ca..539925e471f54c 100644
--- a/Python/getargs.c
+++ b/Python/getargs.c
@@ -2641,6 +2641,11 @@ skipitem(const char **p_format, va_list *p_va, int flags)
if (p_va != NULL) {
(void) va_arg(*p_va, char **);
}
+ if (c == 'w' && *format != '*')
+ {
+ /* after 'w', only '*' is allowed */
+ goto err;
+ }
if (*format == '#') {
if (p_va != NULL) {
(void) va_arg(*p_va, Py_ssize_t *);

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-leave@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: list-python-checkins@lists.gossamer-threads.com