From f22c3d6fe18c3955c53c19a50b21cd09d39b2707 Mon Sep 17 00:00:00 2001 From: Charles Duffy Date: Fri, 19 Jan 2024 17:47:59 -0600 Subject: [PATCH] Avoid private argument-parsing API on Python 3.12 also MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented as an extension of #909, which made this change on Python 3.13 and later. Fixes #926 (build failure observed with clang-16.0.6 and gcc-14). Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) --- CHANGES/909.bugfix.rst | 18 ++++++++-- CHANGES/926.bugfix.rst | 1 + docs/spelling_wordlist.txt | 1 + multidict/_multidict.c | 68 +++++++++++++++++++------------------- tests/test_multidict.py | 4 +++ 5 files changed, 55 insertions(+), 37 deletions(-) create mode 120000 CHANGES/926.bugfix.rst diff --git a/CHANGES/909.bugfix.rst b/CHANGES/909.bugfix.rst index 98966ab0b..00b716983 100644 --- a/CHANGES/909.bugfix.rst +++ b/CHANGES/909.bugfix.rst @@ -1,3 +1,15 @@ -Revert to using the public argument parsing API -:c:func:`PyArg_ParseTupleAndKeywords` under Python 3.13 --- :user:`webknjaz`. +Reverted to using the public argument parsing API +:c:func:`PyArg_ParseTupleAndKeywords` under Python 3.12 +-- by :user:`charles-dyfis-net` and :user:`webknjaz`. + +The effect is that this change prevents build failures with +clang 16.9.6 and gcc-14 reported in :issue:`926`. It also +fixes a segmentation fault crash caused by passing keyword +arguments to :py:meth:`MultiDict.getall() +` discovered by :user:`jonaslb` +and :user:`hroncok` while examining the problem. + +Reversion to public argument parsing API extended to also +apply to Python 3.12 as well to avoid build errors with +clang 16.9.6 and gcc-14 reported in :issue:`926`, via :pr:`929`. +-- :user:`charles-dyfis-net` diff --git a/CHANGES/926.bugfix.rst b/CHANGES/926.bugfix.rst new file mode 120000 index 000000000..1dad3117f --- /dev/null +++ b/CHANGES/926.bugfix.rst @@ -0,0 +1 @@ +909.bugfix.rst \ No newline at end of file diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index f213cbd48..bc96d826f 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -33,6 +33,7 @@ eof fallback fastpath filename +gcc getitem github google diff --git a/multidict/_multidict.c b/multidict/_multidict.c index 228d4df1b..60864953b 100644 --- a/multidict/_multidict.c +++ b/multidict/_multidict.c @@ -9,7 +9,7 @@ #include "_multilib/iter.h" #include "_multilib/views.h" -#if PY_MAJOR_VERSION < 3 || PY_MINOR_VERSION < 13 +#if PY_MAJOR_VERSION < 3 || PY_MINOR_VERSION < 12 #ifndef _PyArg_UnpackKeywords #define FASTCALL_OLD #endif @@ -444,7 +444,7 @@ _multidict_proxy_copy(MultiDictProxyObject *self, PyTypeObject *type) static inline PyObject * multidict_getall( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -458,7 +458,7 @@ multidict_getall( *key = NULL, *_default = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *getall_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:getall", @@ -509,7 +509,7 @@ multidict_getall( static inline PyObject * multidict_getone( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -522,7 +522,7 @@ multidict_getone( PyObject *key = NULL, *_default = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *getone_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:getone", @@ -563,7 +563,7 @@ multidict_getone( static inline PyObject * multidict_get( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -577,7 +577,7 @@ multidict_get( *_default = Py_None, *ret; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *getone_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:getone", @@ -833,7 +833,7 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) static inline PyObject * multidict_add( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -846,7 +846,7 @@ multidict_add( PyObject *key = NULL, *val = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *kwlist[] = {"key", "value", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:add", kwlist, &key, &val)) @@ -913,7 +913,7 @@ multidict_clear(MultiDictObject *self) static inline PyObject * multidict_setdefault( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -926,7 +926,7 @@ multidict_setdefault( PyObject *key = NULL, *_default = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *setdefault_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:setdefault", @@ -967,7 +967,7 @@ multidict_setdefault( static inline PyObject * multidict_popone( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -981,7 +981,7 @@ multidict_popone( *_default = NULL, *ret_val = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *popone_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:popone", @@ -1046,7 +1046,7 @@ multidict_popone( static inline PyObject * multidict_pop( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -1060,7 +1060,7 @@ multidict_pop( *_default = NULL, *ret_val = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *pop_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:popone", @@ -1113,7 +1113,7 @@ multidict_pop( static inline PyObject * multidict_popall( MultiDictObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -1128,7 +1128,7 @@ multidict_popall( *ret_val = NULL; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 static char *popall_keywords[] = {"key", "default", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:popall", @@ -1270,7 +1270,7 @@ static PyMethodDef multidict_methods[] = { { "getall", (PyCFunction)multidict_getall, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1281,7 +1281,7 @@ static PyMethodDef multidict_methods[] = { { "getone", (PyCFunction)multidict_getone, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1292,7 +1292,7 @@ static PyMethodDef multidict_methods[] = { { "get", (PyCFunction)multidict_get, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1321,7 +1321,7 @@ static PyMethodDef multidict_methods[] = { { "add", (PyCFunction)multidict_add, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1350,7 +1350,7 @@ static PyMethodDef multidict_methods[] = { { "setdefault", (PyCFunction)multidict_setdefault, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1361,7 +1361,7 @@ static PyMethodDef multidict_methods[] = { { "popone", (PyCFunction)multidict_popone, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1372,7 +1372,7 @@ static PyMethodDef multidict_methods[] = { { "pop", (PyCFunction)multidict_pop, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1383,7 +1383,7 @@ static PyMethodDef multidict_methods[] = { { "popall", (PyCFunction)multidict_popall, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1559,7 +1559,7 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, static inline PyObject * multidict_proxy_getall( MultiDictProxyObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -1572,7 +1572,7 @@ multidict_proxy_getall( return multidict_getall( self->md, args, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 kwds #else nargs, @@ -1584,7 +1584,7 @@ multidict_proxy_getall( static inline PyObject * multidict_proxy_getone( MultiDictProxyObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -1596,7 +1596,7 @@ multidict_proxy_getone( { return multidict_getone( self->md, args, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 kwds #else nargs, kwnames @@ -1607,7 +1607,7 @@ multidict_proxy_getone( static inline PyObject * multidict_proxy_get( MultiDictProxyObject *self, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 PyObject *args, PyObject *kwds #else @@ -1620,7 +1620,7 @@ multidict_proxy_get( return multidict_get( self->md, args, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 kwds #else nargs, @@ -1734,7 +1734,7 @@ static PyMethodDef multidict_proxy_methods[] = { { "getall", (PyCFunction)multidict_proxy_getall, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1745,7 +1745,7 @@ static PyMethodDef multidict_proxy_methods[] = { { "getone", (PyCFunction)multidict_proxy_getone, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL @@ -1756,7 +1756,7 @@ static PyMethodDef multidict_proxy_methods[] = { { "get", (PyCFunction)multidict_proxy_get, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 METH_VARARGS #else METH_FASTCALL diff --git a/tests/test_multidict.py b/tests/test_multidict.py index 4012a5692..3415065b4 100644 --- a/tests/test_multidict.py +++ b/tests/test_multidict.py @@ -209,6 +209,10 @@ def test_getone(self, cls: Type[MutableMultiMapping[str]]) -> None: assert d.getone("key2", "default") == "default" + def test_call_with_kwargs(self, cls: Type[MultiDict[str]]) -> None: + d = cls([("present", "value")]) + assert d.getall(default="missing", key="notfound") == "missing" + def test__iter__( self, cls: Union[