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 -#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 + #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)", arg, msgbuf, bufsize); + return converterr(nullable, "(impossible)", arg, msgbuf, bufsize); } @@ -2675,6 +2745,9 @@ skipitem(const char **p_format, va_list *p_va, int flags) return "impossible"; } + if (*format == '?') { + format++; + } *p_format = format; return NULL; 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