Skip to content

Commit

Permalink
Avoid private argument-parsing API on Python 3.12 also
Browse files Browse the repository at this point in the history
Implemented as an extension of aio-libs#909, which made this change on Python 3.13 and later.

Fixes aio-libs#926 (build failure observed with clang-16.0.6 and gcc-14).

Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua>
  • Loading branch information
charles-dyfis-net and webknjaz committed Feb 1, 2024
1 parent 3589b18 commit cf3ab55
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 36 deletions.
17 changes: 15 additions & 2 deletions CHANGES/909.bugfix.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
Revert to using the public argument parsing API
:c:func:`PyArg_ParseTupleAndKeywords` under Python 3.13
-- :user:`webknjaz`.
: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()
<multidict.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
:issue:`929`.
-- :user:`charles-dyfis-net`
1 change: 1 addition & 0 deletions CHANGES/926.bugfix.rst
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ eof
fallback
fastpath
filename
gcc
getitem
github
google
Expand Down
68 changes: 34 additions & 34 deletions multidict/_multidict.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/test_multidict.py
Original file line number Diff line number Diff line change
Expand Up @@ -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[
Expand Down

0 comments on commit cf3ab55

Please sign in to comment.