https://github.com/python/cpython/commit/f5f1ac84b3b9d688b9e7d5943c975904b6b74513
commit: f5f1ac84b3b9d688b9e7d5943c975904b6b74513
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-04-08T22:08:00+03:00
summary:
gh-112068: C API: Add support of nullable arguments in PyArg_Parse (GH-121303)
files:
A Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst
M Doc/c-api/arg.rst
M Doc/whatsnew/3.14.rst
M Lib/test/test_capi/test_getargs.py
M Lib/test/test_mmap.py
M Modules/_ctypes/_ctypes.c
M Modules/_interpretersmodule.c
M Modules/_json.c
M Modules/_threadmodule.c
M Modules/mmapmodule.c
M Python/getargs.c
diff --git a/Doc/c-api/arg.rst b/Doc/c-api/arg.rst
index d7b277e9eae03e..81b093a3510914 100644
--- a/Doc/c-api/arg.rst
+++ b/Doc/c-api/arg.rst
@@ -113,14 +113,18 @@ There are three ways strings and buffers can be converted
to C:
``z`` (:class:`str` or ``None``) [const char \*]
Like ``s``, but the Python object may also be ``None``, in which case the C
pointer is set to ``NULL``.
+ It is the same as ``s?`` with the C pointer was initialized to ``NULL``.
``z*`` (:class:`str`, :term:`bytes-like object` or ``None``) [Py_buffer]
Like ``s*``, but the Python object may also be ``None``, in which case the
``buf`` member of the :c:type:`Py_buffer` structure is set to ``NULL``.
+ It is the same as ``s*?`` with the ``buf`` member of the :c:type:`Py_buffer`
+ structure was initialized to ``NULL``.
``z#`` (:class:`str`, read-only :term:`bytes-like object` or ``None``) [const
char \*, :c:type:`Py_ssize_t`]
Like ``s#``, but the Python object may also be ``None``, in which case the C
pointer is set to ``NULL``.
+ It is the same as ``s#?`` with the C pointer was initialized to ``NULL``.
``y`` (read-only :term:`bytes-like object`) [const char \*]
This format converts a bytes-like object to a C pointer to a
@@ -377,6 +381,17 @@ Other objects
Non-tuple sequences are deprecated if *items* contains format units
which store a borrowed buffer or a borrowed reference.
+``unit?`` (anything or ``None``) [*matching-variable(s)*]
+ ``?`` modifies the behavior of the preceding format unit.
+ The C variable(s) corresponding to that parameter should be initialized
+ to their default value --- when the argument is ``None``,
+ :c:func:`PyArg_ParseTuple` does not touch the contents of the corresponding
+ C variable(s).
+ If the argument is not ``None``, it is parsed according to the specified
+ format unit.
+
+ .. versionadded:: next
+
A few other characters have a meaning in a format string. These may not occur
inside nested parentheses. They are:
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 9765bd31333c3c..5f84d8ba8b02c2 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -1846,6 +1846,11 @@ New features
file.
(Contributed by Victor Stinner in :gh:`127350`.)
+* Add support of nullable arguments in :c:func:`PyArg_ParseTuple` and
+ similar functions.
+ Adding ``?`` after any format unit makes ``None`` be accepted as a value.
+ (Contributed by Serhiy Storchaka in :gh:`112068`.)
+
* Add macros :c:func:`Py_PACK_VERSION` and :c:func:`Py_PACK_FULL_VERSION` for
bit-packing Python version numbers.
(Contributed by Petr Viktorin in :gh:`128629`.)
diff --git a/Lib/test/test_capi/test_getargs.py
b/Lib/test/test_capi/test_getargs.py
index 60822d5d794a18..b9cad8d2600e56 100644
--- a/Lib/test/test_capi/test_getargs.py
+++ b/Lib/test/test_capi/test_getargs.py
@@ -1387,6 +1387,123 @@ def test_nested_sequence(self):
"argument 1 must be sequence of length 1, not 0"):
parse(([],), {}, '(' + f + ')', ['a'])
+ def test_specific_type_errors(self):
+ parse = _testcapi.parse_tuple_and_keywords
+
+ def check(format, arg, expected, got='list'):
+ errmsg = f'must be {expected}, not {got}'
+ with self.assertRaisesRegex(TypeError, errmsg):
+ parse((arg,), {}, format, ['a'])
+
+ check('k', [], 'int')
+ check('k?', [], 'int or None')
+ check('K', [], 'int')
+ check('K?', [], 'int or None')
+ check('c', [], 'a byte string of length 1')
+ check('c?', [], 'a byte string of length 1 or None')
+ check('c', b'abc', 'a byte string of length 1',
+ 'a bytes object of length 3')
+ check('c?', b'abc', 'a byte string of length 1 or None',
+ 'a bytes object of length 3')
+ check('c', bytearray(b'abc'), 'a byte string of length 1',
+ 'a bytearray object of length 3')
+ check('c?', bytearray(b'abc'), 'a byte string of length 1 or None',
+ 'a bytearray object of length 3')
+ check('C', [], 'a unicode character')
+ check('C?', [], 'a unicode character or None')
+ check('C', 'abc', 'a unicode character',
+ 'a string of length 3')
+ check('C?', 'abc', 'a unicode character or None',
+ 'a string of length 3')
+ check('s', [], 'str')
+ check('s?', [], 'str or None')
+ check('z', [], 'str or None')
+ check('z?', [], 'str or None')
+ check('es', [], 'str')
+ check('es?', [], 'str or None')
+ check('es#', [], 'str')
+ check('es#?', [], 'str or None')
+ check('et', [], 'str, bytes or bytearray')
+ check('et?', [], 'str, bytes, bytearray or None')
+ check('et#', [], 'str, bytes or bytearray')
+ check('et#?', [], 'str, bytes, bytearray or None')
+ check('w*', [], 'read-write bytes-like object')
+ check('w*?', [], 'read-write bytes-like object or None')
+ check('S', [], 'bytes')
+ check('S?', [], 'bytes or None')
+ check('U', [], 'str')
+ check('U?', [], 'str or None')
+ check('Y', [], 'bytearray')
+ check('Y?', [], 'bytearray or None')
+ check('(OO)', 42, '2-item tuple', 'int')
+ check('(OO)?', 42, '2-item tuple or None', 'int')
+ check('(OO)', (1, 2, 3), 'tuple of length 2', '3')
+
+ def test_nullable(self):
+ parse = _testcapi.parse_tuple_and_keywords
+
+ def check(format, arg, allows_none=False):
+ # Because some format units (such as y*) require cleanup,
+ # we force the parsing code to perform the cleanup by adding
+ # an argument that always fails.
+ # By checking for an exception, we ensure that the parsing
+ # of the first argument was successful.
+ self.assertRaises(OverflowError, parse,
+ (arg, 256), {}, format + '?b', ['a', 'b'])
+ self.assertRaises(OverflowError, parse,
+ (None, 256), {}, format + '?b', ['a', 'b'])
+ self.assertRaises(OverflowError, parse,
+ (arg, 256), {}, format + 'b', ['a', 'b'])
+ self.assertRaises(OverflowError if allows_none else TypeError,
parse,
+ (None, 256), {}, format + 'b', ['a', 'b'])
+
+ check('b', 42)
+ check('B', 42)
+ check('h', 42)
+ check('H', 42)
+ check('i', 42)
+ check('I', 42)
+ check('n', 42)
+ check('l', 42)
+ check('k', 42)
+ check('L', 42)
+ check('K', 42)
+ check('f', 2.5)
+ check('d', 2.5)
+ check('D', 2.5j)
+ check('c', b'a')
+ check('C', 'a')
+ check('p', True, allows_none=True)
+ check('y', b'buffer')
+ check('y*', b'buffer')
+ check('y#', b'buffer')
+ check('s', 'string')
+ check('s*', 'string')
+ check('s#', 'string')
+ check('z', 'string', allows_none=True)
+ check('z*', 'string', allows_none=True)
+ check('z#', 'string', allows_none=True)
+ check('w*', bytearray(b'buffer'))
+ check('U', 'string')
+ check('S', b'bytes')
+ check('Y', bytearray(b'bytearray'))
+ check('O', object, allows_none=True)
+
+ check('(OO)', (1, 2))
+ self.assertEqual(parse((((1, 2), 3),), {}, '((OO)?O)', ['a']), (1, 2,
3))
+ self.assertEqual(parse(((None, 3),), {}, '((OO)?O)', ['a']), (NULL,
NULL, 3))
+ self.assertEqual(parse((((1, 2), 3),), {}, '((OO)O)', ['a']), (1, 2,
3))
+ self.assertRaises(TypeError, parse, ((None, 3),), {}, '((OO)O)', ['a'])
+
+ parse((None,), {}, 'es?', ['a'])
+ parse((None,), {}, 'es#?', ['a'])
+ parse((None,), {}, 'et?', ['a'])
+ parse((None,), {}, 'et#?', ['a'])
+ parse((None,), {}, 'O!?', ['a'])
+ parse((None,), {}, 'O&?', ['a'])
+
+ # TODO: More tests for es?, es#?, et?, et#?, O!, O&
+
@unittest.skipIf(_testinternalcapi is None, 'needs _testinternalcapi')
def test_gh_119213(self):
rc, out, err = script_helper.assert_python_ok("-c", """if True:
diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
index b2a299ed172967..fd4197b7086976 100644
--- a/Lib/test/test_mmap.py
+++ b/Lib/test/test_mmap.py
@@ -732,7 +732,7 @@ def test_tagname(self):
m2.close()
m1.close()
- with self.assertRaisesRegex(TypeError, 'tagname'):
+ with self.assertRaisesRegex(TypeError, 'must be str or None'):
mmap.mmap(-1, 8, tagname=1)
@cpython_only
diff --git
a/Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst
b/Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst
new file mode 100644
index 00000000000000..d49b1735825e8a
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-01-08-18-55-57.gh-issue-112068.ofI5Fl.rst
@@ -0,0 +1,3 @@
+Add support of nullable arguments in :c:func:`PyArg_Parse` and similar
+functions. Adding ``?`` after any format unit makes ``None`` be accepted as
+a value.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index bba006772efbfa..55e5eee0eb081a 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -3848,9 +3848,7 @@ _validate_paramflags(ctypes_state *st, PyTypeObject
*type, PyObject *paramflags)
PyObject *name = Py_None;
PyObject *defval;
PyObject *typ;
- if (!PyArg_ParseTuple(item, "i|OO", &flag, &name, &defval) ||
- !(name == Py_None || PyUnicode_Check(name)))
- {
+ if (!PyArg_ParseTuple(item, "i|U?O", &flag, &name, &defval)) {
PyErr_SetString(PyExc_TypeError,
"paramflags must be a sequence of (int [,string [,value]])
tuples");
return 0;
@@ -3915,10 +3913,8 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args,
PyObject *kwds)
void *handle;
PyObject *paramflags = NULL;
- if (!PyArg_ParseTuple(args, "O|O", &ftuple, ¶mflags))
+ if (!PyArg_ParseTuple(args, "O|O?", &ftuple, ¶mflags))
return NULL;
- if (paramflags == Py_None)
- paramflags = NULL;
ftuple = PySequence_Tuple(ftuple);
if (!ftuple)
@@ -4050,10 +4046,8 @@ PyCFuncPtr_FromVtblIndex(PyTypeObject *type, PyObject
*args, PyObject *kwds)
GUID *iid = NULL;
Py_ssize_t iid_len = 0;
- if (!PyArg_ParseTuple(args, "is|Oz#", &index, &name, ¶mflags, &iid,
&iid_len))
+ if (!PyArg_ParseTuple(args, "is|O?z#", &index, &name, ¶mflags, &iid,
&iid_len))
return NULL;
- if (paramflags == Py_None)
- paramflags = NULL;
ctypes_state *st = get_module_state_by_def(Py_TYPE(type));
if (!_validate_paramflags(st, type, paramflags)) {
diff --git a/Modules/_interpretersmodule.c b/Modules/_interpretersmodule.c
index 74f1c02cfab4c9..c444c4c32c71e3 100644
--- a/Modules/_interpretersmodule.c
+++ b/Modules/_interpretersmodule.c
@@ -1252,14 +1252,11 @@ interp_get_config(PyObject *self, PyObject *args,
PyObject *kwds)
PyObject *idobj = NULL;
int restricted = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "O|$p:get_config", kwlist,
+ "O?|$p:get_config", kwlist,
&idobj, &restricted))
{
return NULL;
}
- if (idobj == Py_None) {
- idobj = NULL;
- }
int reqready = 0;
PyInterpreterState *interp = \
@@ -1376,14 +1373,14 @@ capture_exception(PyObject *self, PyObject *args,
PyObject *kwds)
static char *kwlist[] = {"exc", NULL};
PyObject *exc_arg = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds,
- "|O:capture_exception", kwlist,
+ "|O?:capture_exception", kwlist,
&exc_arg))
{
return NULL;
}
PyObject *exc = exc_arg;
- if (exc == NULL || exc == Py_None) {
+ if (exc == NULL) {
exc = PyErr_GetRaisedException();
if (exc == NULL) {
Py_RETURN_NONE;
diff --git a/Modules/_json.c b/Modules/_json.c
index cd8e697916226b..89b0a41dd10acb 100644
--- a/Modules/_json.c
+++ b/Modules/_json.c
@@ -1228,23 +1228,16 @@ encoder_new(PyTypeObject *type, PyObject *args,
PyObject *kwds)
static char *kwlist[] = {"markers", "default", "encoder", "indent",
"key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL};
PyEncoderObject *s;
- PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
+ PyObject *markers = Py_None, *defaultfn, *encoder, *indent, *key_separator;
PyObject *item_separator;
int sort_keys, skipkeys, allow_nan;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOUUppp:make_encoder",
kwlist,
- &markers, &defaultfn, &encoder, &indent,
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!?OOOUUppp:make_encoder",
kwlist,
+ &PyDict_Type, &markers, &defaultfn, &encoder, &indent,
&key_separator, &item_separator,
&sort_keys, &skipkeys, &allow_nan))
return NULL;
- if (markers != Py_None && !PyDict_Check(markers)) {
- PyErr_Format(PyExc_TypeError,
- "make_encoder() argument 1 must be dict or None, "
- "not %.200s", Py_TYPE(markers)->tp_name);
- return NULL;
- }
-
s = (PyEncoderObject *)type->tp_alloc(type, 0);
if (s == NULL)
return NULL;
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index 9f6ac21c8a8ccf..6967f7ef42f173 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -651,12 +651,12 @@ PyThreadHandleObject_join(PyObject *op, PyObject *args)
PyThreadHandleObject *self = PyThreadHandleObject_CAST(op);
PyObject *timeout_obj = NULL;
- if (!PyArg_ParseTuple(args, "|O:join", &timeout_obj)) {
+ if (!PyArg_ParseTuple(args, "|O?:join", &timeout_obj)) {
return NULL;
}
PyTime_t timeout_ns = -1;
- if (timeout_obj != NULL && timeout_obj != Py_None) {
+ if (timeout_obj != NULL) {
if (_PyTime_FromSecondsObject(&timeout_ns, timeout_obj,
_PyTime_ROUND_TIMEOUT) < 0) {
return NULL;
@@ -1919,10 +1919,10 @@ thread_PyThread_start_joinable_thread(PyObject *module,
PyObject *fargs,
PyObject *func = NULL;
int daemon = 1;
thread_module_state *state = get_thread_state(module);
- PyObject *hobj = NULL;
+ PyObject *hobj = Py_None;
if (!PyArg_ParseTupleAndKeywords(fargs, fkwargs,
- "O|Op:start_joinable_thread", keywords,
- &func, &hobj, &daemon)) {
+ "O|O!?p:start_joinable_thread", keywords,
+ &func, state->thread_handle_type, &hobj,
&daemon)) {
return NULL;
}
@@ -1932,14 +1932,6 @@ thread_PyThread_start_joinable_thread(PyObject *module,
PyObject *fargs,
return NULL;
}
- if (hobj == NULL) {
- hobj = Py_None;
- }
- else if (hobj != Py_None && !Py_IS_TYPE(hobj, state->thread_handle_type)) {
- PyErr_SetString(PyExc_TypeError, "'handle' must be a _ThreadHandle");
- return NULL;
- }
-
if (PySys_Audit("_thread.start_joinable_thread", "OiO", func, daemon,
hobj) < 0) {
return NULL;
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index 67fd6db2f361d6..6a385562845849 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -23,7 +23,6 @@
#endif
#include <Python.h>
-#include "pycore_abstract.h" // _Py_convert_optional_to_ssize_t()
#include "pycore_bytesobject.h" // _PyBytes_Find()
#include "pycore_fileutils.h" // _Py_stat_struct
@@ -516,7 +515,7 @@ mmap_read_method(PyObject *op, PyObject *args)
mmap_object *self = mmap_object_CAST(op);
CHECK_VALID(NULL);
- if (!PyArg_ParseTuple(args, "|O&:read", _Py_convert_optional_to_ssize_t,
&num_bytes))
+ if (!PyArg_ParseTuple(args, "|n?:read", &num_bytes))
return NULL;
CHECK_VALID(NULL);
@@ -1710,7 +1709,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args,
PyObject *kwdict)
DWORD off_lo; /* lower 32 bits of offset */
DWORD size_hi; /* upper 32 bits of size */
DWORD size_lo; /* lower 32 bits of size */
- PyObject *tagname = Py_None;
+ PyObject *tagname = NULL;
DWORD dwErr = 0;
int fileno;
HANDLE fh = 0;
@@ -1720,7 +1719,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args,
PyObject *kwdict)
"tagname",
"access", "offset", NULL };
- if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|OiL", keywords,
+ if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|U?iL", keywords,
&fileno, &map_size,
&tagname, &access, &offset)) {
return NULL;
@@ -1853,13 +1852,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args,
PyObject *kwdict)
m_obj->weakreflist = NULL;
m_obj->exports = 0;
/* set the tag name */
- if (!Py_IsNone(tagname)) {
- if (!PyUnicode_Check(tagname)) {
- Py_DECREF(m_obj);
- return PyErr_Format(PyExc_TypeError, "expected str or None for "
- "'tagname', not %.200s",
- Py_TYPE(tagname)->tp_name);
- }
+ if (tagname != NULL) {
m_obj->tagname = PyUnicode_AsWideCharString(tagname, NULL);
if (m_obj->tagname == NULL) {
Py_DECREF(m_obj);
diff --git a/Python/getargs.c b/Python/getargs.c
index 08325ca5a87c49..16d5e52742d129 100644
--- a/Python/getargs.c
+++ b/Python/getargs.c
@@ -1,6 +1,8 @@
/* New getargs implementation */
+#include <stdbool.h>
+
#define PY_CXX_CONST const
#include "Python.h"
#include "pycore_abstract.h" // _PyNumber_Index()
@@ -466,9 +468,12 @@ converttuple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
const char *format = *p_format;
int i;
Py_ssize_t len;
+ bool nullable = false;
int istuple = PyTuple_Check(arg);
int mustbetuple = istuple;
+ assert(*format == '(');
+ format++;
for (;;) {
int c = *format++;
if (c == '(') {
@@ -477,8 +482,12 @@ converttuple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
level++;
}
else if (c == ')') {
- if (level == 0)
+ if (level == 0) {
+ if (*format == '?') {
+ nullable = true;
+ }
break;
+ }
level--;
}
else if (c == ':' || c == ';' || c == '\0')
@@ -515,6 +524,13 @@ converttuple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
}
}
+ if (arg == Py_None && nullable) {
+ const char *msg = skipitem(p_format, p_va, flags);
+ if (msg != NULL) {
+ levels[0] = 0;
+ }
+ return msg;
+ }
if (istuple) {
/* fallthrough */
}
@@ -523,9 +539,10 @@ converttuple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
{
levels[0] = 0;
PyOS_snprintf(msgbuf, bufsize,
- "must be %d-item tuple, not %.50s",
- n,
- arg == Py_None ? "None" : Py_TYPE(arg)->tp_name);
+ "must be %d-item tuple%s, not %.50s",
+ n,
+ nullable ? " or None" : "",
+ arg == Py_None ? "None" : Py_TYPE(arg)->tp_name);
return msgbuf;
}
else {
@@ -562,7 +579,7 @@ converttuple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
return msgbuf;
}
- format = *p_format;
+ format = *p_format + 1;
for (i = 0; i < n; i++) {
const char *msg;
PyObject *item = PyTuple_GET_ITEM(arg, i);
@@ -577,6 +594,10 @@ converttuple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
}
}
+ format++;
+ if (*format == '?') {
+ format++;
+ }
*p_format = format;
if (!istuple) {
Py_DECREF(arg);
@@ -595,11 +616,8 @@ convertitem(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
const char *format = *p_format;
if (*format == '(' /* ')' */) {
- format++;
msg = converttuple(arg, &format, p_va, flags, levels, msgbuf,
bufsize, freelist);
- if (msg == NULL)
- format++;
}
else {
msg = convertsimple(arg, &format, p_va, flags,
@@ -629,7 +647,7 @@ _PyArg_BadArgument(const char *fname, const char
*displayname,
}
static const char *
-converterr(const char *expected, PyObject *arg, char *msgbuf, size_t bufsize)
+converterr(bool nullable, const char *expected, PyObject *arg, char *msgbuf,
size_t bufsize)
{
assert(expected != NULL);
assert(arg != NULL);
@@ -639,20 +657,23 @@ converterr(const char *expected, PyObject *arg, char
*msgbuf, size_t bufsize)
}
else {
PyOS_snprintf(msgbuf, bufsize,
- "must be %.50s, not %.50s", expected,
+ "must be %.50s%s, not %.50s", expected,
+ nullable ? " or None" : "",
arg == Py_None ? "None" : Py_TYPE(arg)->tp_name);
}
return msgbuf;
}
static const char *
-convertcharerr(const char *expected, const char *what, Py_ssize_t size,
+convertcharerr(bool nullable, const char *expected, const char *what,
Py_ssize_t size,
char *msgbuf, size_t bufsize)
{
assert(expected != NULL);
PyOS_snprintf(msgbuf, bufsize,
- "must be %.50s, not %.50s of length %zd",
- expected, what, size);
+ "must be %.50s%s, not %.50s of length %zd",
+ expected,
+ nullable ? " or None" : "",
+ what, size);
return msgbuf;
}
@@ -672,15 +693,26 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
char *msgbuf, size_t bufsize, freelist_t *freelist)
{
#define RETURN_ERR_OCCURRED return msgbuf
+#define HANDLE_NULLABLE \
+ if (*format == '?') { \
+ format++; \
+ if (arg == Py_None) { \
+ break; \
+ } \
+ nullable = true; \
+ }
+
const char *format = *p_format;
char c = *format++;
const char *sarg;
+ bool nullable = false;
switch (c) {
case 'b': { /* unsigned byte -- very short int */
unsigned char *p = va_arg(*p_va, unsigned char *);
+ HANDLE_NULLABLE;
long ival = PyLong_AsLong(arg);
if (ival == -1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -694,7 +726,6 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
"unsigned byte integer is greater than maximum");
RETURN_ERR_OCCURRED;
}
- else
*p = (unsigned char) ival;
break;
}
@@ -702,6 +733,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'B': {/* byte sized bitfield - both signed and unsigned
values allowed */
unsigned char *p = va_arg(*p_va, unsigned char *);
+ HANDLE_NULLABLE;
unsigned long ival = PyLong_AsUnsignedLongMask(arg);
if (ival == (unsigned long)-1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -712,6 +744,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'h': {/* signed short int */
short *p = va_arg(*p_va, short *);
+ HANDLE_NULLABLE;
long ival = PyLong_AsLong(arg);
if (ival == -1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -733,6 +766,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'H': { /* short int sized bitfield, both signed and
unsigned allowed */
unsigned short *p = va_arg(*p_va, unsigned short *);
+ HANDLE_NULLABLE;
unsigned long ival = PyLong_AsUnsignedLongMask(arg);
if (ival == (unsigned long)-1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -743,6 +777,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'i': {/* signed int */
int *p = va_arg(*p_va, int *);
+ HANDLE_NULLABLE;
long ival = PyLong_AsLong(arg);
if (ival == -1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -764,6 +799,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'I': { /* int sized bitfield, both signed and
unsigned allowed */
unsigned int *p = va_arg(*p_va, unsigned int *);
+ HANDLE_NULLABLE;
unsigned long ival = PyLong_AsUnsignedLongMask(arg);
if (ival == (unsigned long)-1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -776,6 +812,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
{
PyObject *iobj;
Py_ssize_t *p = va_arg(*p_va, Py_ssize_t *);
+ HANDLE_NULLABLE;
Py_ssize_t ival = -1;
iobj = _PyNumber_Index(arg);
if (iobj != NULL) {
@@ -789,6 +826,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
}
case 'l': {/* long int */
long *p = va_arg(*p_va, long *);
+ HANDLE_NULLABLE;
long ival = PyLong_AsLong(arg);
if (ival == -1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -799,17 +837,19 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
case 'k': { /* long sized bitfield */
unsigned long *p = va_arg(*p_va, unsigned long *);
+ HANDLE_NULLABLE;
unsigned long ival;
if (PyLong_Check(arg))
ival = PyLong_AsUnsignedLongMask(arg);
else
- return converterr("int", arg, msgbuf, bufsize);
+ return converterr(nullable, "int", arg, msgbuf, bufsize);
*p = ival;
break;
}
case 'L': {/* long long */
long long *p = va_arg( *p_va, long long * );
+ HANDLE_NULLABLE;
long long ival = PyLong_AsLongLong(arg);
if (ival == (long long)-1 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -820,17 +860,19 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
case 'K': { /* long long sized bitfield */
unsigned long long *p = va_arg(*p_va, unsigned long long *);
+ HANDLE_NULLABLE;
unsigned long long ival;
if (PyLong_Check(arg))
ival = PyLong_AsUnsignedLongLongMask(arg);
else
- return converterr("int", arg, msgbuf, bufsize);
+ return converterr(nullable, "int", arg, msgbuf, bufsize);
*p = ival;
break;
}
case 'f': {/* float */
float *p = va_arg(*p_va, float *);
+ HANDLE_NULLABLE;
double dval = PyFloat_AsDouble(arg);
if (dval == -1.0 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -841,6 +883,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'd': {/* double */
double *p = va_arg(*p_va, double *);
+ HANDLE_NULLABLE;
double dval = PyFloat_AsDouble(arg);
if (dval == -1.0 && PyErr_Occurred())
RETURN_ERR_OCCURRED;
@@ -851,6 +894,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'D': {/* complex double */
Py_complex *p = va_arg(*p_va, Py_complex *);
+ HANDLE_NULLABLE;
Py_complex cval;
cval = PyComplex_AsCComplex(arg);
if (PyErr_Occurred())
@@ -862,9 +906,10 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
case 'c': {/* char */
char *p = va_arg(*p_va, char *);
+ HANDLE_NULLABLE;
if (PyBytes_Check(arg)) {
if (PyBytes_GET_SIZE(arg) != 1) {
- return convertcharerr("a byte string of length 1",
+ return convertcharerr(nullable, "a byte string of length 1",
"a bytes object", PyBytes_GET_SIZE(arg),
msgbuf, bufsize);
}
@@ -872,27 +917,28 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
}
else if (PyByteArray_Check(arg)) {
if (PyByteArray_GET_SIZE(arg) != 1) {
- return convertcharerr("a byte string of length 1",
+ return convertcharerr(nullable, "a byte string of length 1",
"a bytearray object",
PyByteArray_GET_SIZE(arg),
msgbuf, bufsize);
}
*p = PyByteArray_AS_STRING(arg)[0];
}
else
- return converterr("a byte string of length 1", arg, msgbuf,
bufsize);
+ return converterr(nullable, "a byte string of length 1", arg,
msgbuf, bufsize);
break;
}
case 'C': {/* unicode char */
int *p = va_arg(*p_va, int *);
+ HANDLE_NULLABLE;
int kind;
const void *data;
if (!PyUnicode_Check(arg))
- return converterr("a unicode character", arg, msgbuf, bufsize);
+ return converterr(nullable, "a unicode character", arg, msgbuf,
bufsize);
if (PyUnicode_GET_LENGTH(arg) != 1) {
- return convertcharerr("a unicode character",
+ return convertcharerr(nullable, "a unicode character",
"a string", PyUnicode_GET_LENGTH(arg),
msgbuf, bufsize);
}
@@ -905,6 +951,7 @@ convertsimple(PyObject *arg, const char **p_format, va_list
*p_va, int flags,
case 'p': {/* boolean *p*redicate */
int *p = va_arg(*p_va, int *);
+ HANDLE_NULLABLE;
int val = PyObject_IsTrue(arg);
if (val > 0)
*p = 1;
@@ -923,24 +970,31 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
const char *buf;
Py_ssize_t count;
if (*format == '*') {
- if (getbuffer(arg, (Py_buffer*)p, &buf) < 0)
- return converterr(buf, arg, msgbuf, bufsize);
format++;
+ HANDLE_NULLABLE;
+ if (getbuffer(arg, (Py_buffer*)p, &buf) < 0)
+ return converterr(nullable, buf, arg, msgbuf, bufsize);
if (addcleanup(p, freelist, cleanup_buffer)) {
return converterr(
- "(cleanup problem)",
+ nullable, "(cleanup problem)",
arg, msgbuf, bufsize);
}
break;
}
- count = convertbuffer(arg, (const void **)p, &buf);
- if (count < 0)
- return converterr(buf, arg, msgbuf, bufsize);
- if (*format == '#') {
+ else if (*format == '#') {
Py_ssize_t *psize = va_arg(*p_va, Py_ssize_t*);
- *psize = count;
format++;
- } else {
+ HANDLE_NULLABLE;
+ count = convertbuffer(arg, (const void **)p, &buf);
+ if (count < 0)
+ return converterr(nullable, buf, arg, msgbuf, bufsize);
+ *psize = count;
+ }
+ else {
+ HANDLE_NULLABLE;
+ count = convertbuffer(arg, (const void **)p, &buf);
+ if (count < 0)
+ return converterr(nullable, buf, arg, msgbuf, bufsize);
if (strlen(*p) != (size_t)count) {
PyErr_SetString(PyExc_ValueError, "embedded null byte");
RETURN_ERR_OCCURRED;
@@ -956,32 +1010,35 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
/* "s*" or "z*" */
Py_buffer *p = (Py_buffer *)va_arg(*p_va, Py_buffer *);
+ format++;
+ HANDLE_NULLABLE;
if (c == 'z' && arg == Py_None)
PyBuffer_FillInfo(p, NULL, NULL, 0, 1, 0);
else if (PyUnicode_Check(arg)) {
Py_ssize_t len;
sarg = PyUnicode_AsUTF8AndSize(arg, &len);
if (sarg == NULL)
- return converterr(CONV_UNICODE,
+ return converterr(nullable, CONV_UNICODE,
arg, msgbuf, bufsize);
PyBuffer_FillInfo(p, arg, (void *)sarg, len, 1, 0);
}
else { /* any bytes-like object */
const char *buf;
if (getbuffer(arg, p, &buf) < 0)
- return converterr(buf, arg, msgbuf, bufsize);
+ return converterr(nullable, buf, arg, msgbuf, bufsize);
}
if (addcleanup(p, freelist, cleanup_buffer)) {
return converterr(
- "(cleanup problem)",
+ nullable, "(cleanup problem)",
arg, msgbuf, bufsize);
}
- format++;
} else if (*format == '#') { /* a string or read-only bytes-like
object */
/* "s#" or "z#" */
const void **p = (const void **)va_arg(*p_va, const char **);
Py_ssize_t *psize = va_arg(*p_va, Py_ssize_t*);
+ format++;
+ HANDLE_NULLABLE;
if (c == 'z' && arg == Py_None) {
*p = NULL;
*psize = 0;
@@ -990,7 +1047,7 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
Py_ssize_t len;
sarg = PyUnicode_AsUTF8AndSize(arg, &len);
if (sarg == NULL)
- return converterr(CONV_UNICODE,
+ return converterr(nullable, CONV_UNICODE,
arg, msgbuf, bufsize);
*p = sarg;
*psize = len;
@@ -1000,22 +1057,22 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
const char *buf;
Py_ssize_t count = convertbuffer(arg, p, &buf);
if (count < 0)
- return converterr(buf, arg, msgbuf, bufsize);
+ return converterr(nullable, buf, arg, msgbuf, bufsize);
*psize = count;
}
- format++;
} else {
/* "s" or "z" */
const char **p = va_arg(*p_va, const char **);
Py_ssize_t len;
sarg = NULL;
+ HANDLE_NULLABLE;
if (c == 'z' && arg == Py_None)
*p = NULL;
else if (PyUnicode_Check(arg)) {
sarg = PyUnicode_AsUTF8AndSize(arg, &len);
if (sarg == NULL)
- return converterr(CONV_UNICODE,
+ return converterr(nullable, CONV_UNICODE,
arg, msgbuf, bufsize);
if (strlen(sarg) != (size_t)len) {
PyErr_SetString(PyExc_ValueError, "embedded null
character");
@@ -1024,7 +1081,7 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
*p = sarg;
}
else
- return converterr(c == 'z' ? "str or None" : "str",
+ return converterr(c == 'z' || nullable, "str",
arg, msgbuf, bufsize);
}
break;
@@ -1053,13 +1110,46 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
recode_strings = 0;
else
return converterr(
- "(unknown parser marker combination)",
+ nullable, "(unknown parser marker combination)",
arg, msgbuf, bufsize);
buffer = (char **)va_arg(*p_va, char **);
format++;
if (buffer == NULL)
- return converterr("(buffer is NULL)",
+ return converterr(nullable, "(buffer is NULL)",
arg, msgbuf, bufsize);
+ Py_ssize_t *psize = NULL;
+ if (*format == '#') {
+ /* Using buffer length parameter '#':
+
+ - if *buffer is NULL, a new buffer of the
+ needed size is allocated and the data
+ copied into it; *buffer is updated to point
+ to the new buffer; the caller is
+ responsible for PyMem_Free()ing it after
+ usage
+
+ - if *buffer is not NULL, the data is
+ copied to *buffer; *buffer_len has to be
+ set to the size of the buffer on input;
+ buffer overflow is signalled with an error;
+ buffer has to provide enough room for the
+ encoded string plus the trailing 0-byte
+
+ - in both cases, *buffer_len is updated to
+ the size of the buffer /excluding/ the
+ trailing 0-byte
+
+ */
+ psize = va_arg(*p_va, Py_ssize_t*);
+
+ format++;
+ if (psize == NULL) {
+ return converterr(
+ nullable, "(buffer_len is NULL)",
+ arg, msgbuf, bufsize);
+ }
+ }
+ HANDLE_NULLABLE;
/* Encode object */
if (!recode_strings &&
@@ -1080,7 +1170,7 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
encoding,
NULL);
if (s == NULL)
- return converterr("(encoding failed)",
+ return converterr(nullable, "(encoding failed)",
arg, msgbuf, bufsize);
assert(PyBytes_Check(s));
size = PyBytes_GET_SIZE(s);
@@ -1090,42 +1180,15 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
}
else {
return converterr(
- recode_strings ? "str" : "str, bytes or bytearray",
+ nullable,
+ recode_strings ? "str"
+ : nullable ? "str, bytes, bytearray"
+ : "str, bytes or bytearray",
arg, msgbuf, bufsize);
}
/* Write output; output is guaranteed to be 0-terminated */
- if (*format == '#') {
- /* Using buffer length parameter '#':
-
- - if *buffer is NULL, a new buffer of the
- needed size is allocated and the data
- copied into it; *buffer is updated to point
- to the new buffer; the caller is
- responsible for PyMem_Free()ing it after
- usage
-
- - if *buffer is not NULL, the data is
- copied to *buffer; *buffer_len has to be
- set to the size of the buffer on input;
- buffer overflow is signalled with an error;
- buffer has to provide enough room for the
- encoded string plus the trailing 0-byte
-
- - in both cases, *buffer_len is updated to
- the size of the buffer /excluding/ the
- trailing 0-byte
-
- */
- Py_ssize_t *psize = va_arg(*p_va, Py_ssize_t*);
-
- format++;
- if (psize == NULL) {
- Py_DECREF(s);
- return converterr(
- "(buffer_len is NULL)",
- arg, msgbuf, bufsize);
- }
+ if (psize != NULL) {
if (*buffer == NULL) {
*buffer = PyMem_NEW(char, size + 1);
if (*buffer == NULL) {
@@ -1136,7 +1199,7 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
if (addcleanup(buffer, freelist, cleanup_ptr)) {
Py_DECREF(s);
return converterr(
- "(cleanup problem)",
+ nullable, "(cleanup problem)",
arg, msgbuf, bufsize);
}
} else {
@@ -1170,7 +1233,7 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
if ((Py_ssize_t)strlen(ptr) != size) {
Py_DECREF(s);
return converterr(
- "encoded string without null bytes",
+ nullable, "encoded string without null bytes",
arg, msgbuf, bufsize);
}
*buffer = PyMem_NEW(char, size + 1);
@@ -1181,7 +1244,7 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
}
if (addcleanup(buffer, freelist, cleanup_ptr)) {
Py_DECREF(s);
- return converterr("(cleanup problem)",
+ return converterr(nullable, "(cleanup problem)",
arg, msgbuf, bufsize);
}
memcpy(*buffer, ptr, size+1);
@@ -1192,29 +1255,32 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
case 'S': { /* PyBytes object */
PyObject **p = va_arg(*p_va, PyObject **);
+ HANDLE_NULLABLE;
if (PyBytes_Check(arg))
*p = arg;
else
- return converterr("bytes", arg, msgbuf, bufsize);
+ return converterr(nullable, "bytes", arg, msgbuf, bufsize);
break;
}
case 'Y': { /* PyByteArray object */
PyObject **p = va_arg(*p_va, PyObject **);
+ HANDLE_NULLABLE;
if (PyByteArray_Check(arg))
*p = arg;
else
- return converterr("bytearray", arg, msgbuf, bufsize);
+ return converterr(nullable, "bytearray", arg, msgbuf, bufsize);
break;
}
case 'U': { /* PyUnicode object */
PyObject **p = va_arg(*p_va, PyObject **);
+ HANDLE_NULLABLE;
if (PyUnicode_Check(arg)) {
*p = arg;
}
else
- return converterr("str", arg, msgbuf, bufsize);
+ return converterr(nullable, "str", arg, msgbuf, bufsize);
break;
}
@@ -1225,10 +1291,11 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
type = va_arg(*p_va, PyTypeObject*);
p = va_arg(*p_va, PyObject **);
format++;
+ HANDLE_NULLABLE;
if (PyType_IsSubtype(Py_TYPE(arg), type))
*p = arg;
else
- return converterr(type->tp_name, arg, msgbuf, bufsize);
+ return converterr(nullable, type->tp_name, arg, msgbuf,
bufsize);
}
else if (*format == '&') {
@@ -1237,16 +1304,18 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
void *addr = va_arg(*p_va, void *);
int res;
format++;
+ HANDLE_NULLABLE;
if (! (res = (*convert)(arg, addr)))
- return converterr("(unspecified)",
+ return converterr(nullable, "(unspecified)",
arg, msgbuf, bufsize);
if (res == Py_CLEANUP_SUPPORTED &&
addcleanup(addr, freelist, convert) == -1)
- return converterr("(cleanup problem)",
+ return converterr(nullable, "(cleanup problem)",
arg, msgbuf, bufsize);
}
else {
p = va_arg(*p_va, PyObject **);
+ HANDLE_NULLABLE;
*p = arg;
}
break;
@@ -1258,29 +1327,30 @@ convertsimple(PyObject *arg, const char **p_format,
va_list *p_va, int flags,
if (*format != '*')
return converterr(
- "(invalid use of 'w' format character)",
+ nullable, "(invalid use of 'w' format character)",
arg, msgbuf, bufsize);
format++;
+ HANDLE_NULLABLE;
/* Caller is interested in Py_buffer, and the object supports it
directly. The request implicitly asks for PyBUF_SIMPLE, so the
result is C-contiguous with format 'B'. */
if (PyObject_GetBuffer(arg, (Py_buffer*)p, PyBUF_WRITABLE) < 0) {
PyErr_Clear();
- return converterr("read-write bytes-like object",
+ return converterr(nullable, "read-write bytes-like object",
arg, msgbuf, bufsize);
}
assert(PyBuffer_IsContiguous((Py_buffer *)p, 'C'));
if (addcleanup(p, freelist, cleanup_buffer)) {
return converterr(
- "(cleanup problem)",
+ nullable, "(cleanup problem)",
arg, msgbuf, bufsize);
}
break;
}
default:
- return converterr("(impossible<bad format char>)", arg, msgbuf,
bufsize);
+ return converterr(nullable, "(impossible<bad format char>)", arg,
msgbuf, bufsize);
}
@@ -2675,6 +2745,9 @@ skipitem(const char **p_format, va_list *p_va, int flags)
return "impossible<bad format char>";
}
+ if (*format == '?') {
+ format++;
+ }
*p_format = format;
return NULL;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]