Skip to content

Commit 36b35bc

Browse files
ctypes: use the correct ABI for variadic functions
On arm64 the calling convention for variardic functions is different than the convention for fixed-arg functions of the same arg types. ctypes needs to use ffi_prep_cif_var to tell libffi which calling convention to use.
1 parent ff883bc commit 36b35bc

File tree

6 files changed

+85
-12
lines changed

6 files changed

+85
-12
lines changed

Doc/library/ctypes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,6 +1579,13 @@ They are instances of a private class:
15791579
value usable as argument (integer, string, ctypes instance). This allows
15801580
defining adapters that can adapt custom objects as function parameters.
15811581

1582+
.. attribute:: variadic
1583+
1584+
Assign a boolean to specify that the function takes a variable nubmer of
1585+
arguments. This does not matter on most platforms, but for Apple arm64
1586+
platforms variadic functions have a different calling convention than
1587+
normal functions.
1588+
15821589
.. attribute:: errcheck
15831590

15841591
Assign a Python function or another callable to this attribute. The

Lib/test/test_bytes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,8 @@ def test_from_format(self):
10341034
c_char_p)
10351035

10361036
PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
1037+
PyBytes_FromFormat.variadic = True
1038+
PyBytes_FromFormat.argtypes = (c_char_p,)
10371039
PyBytes_FromFormat.restype = py_object
10381040

10391041
# basic tests

Lib/test/test_unicode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,11 +2509,14 @@ class CAPITest(unittest.TestCase):
25092509
def test_from_format(self):
25102510
support.import_module('ctypes')
25112511
from ctypes import (
2512+
c_char_p,
25122513
pythonapi, py_object, sizeof,
25132514
c_int, c_long, c_longlong, c_ssize_t,
25142515
c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p)
25152516
name = "PyUnicode_FromFormat"
25162517
_PyUnicode_FromFormat = getattr(pythonapi, name)
2518+
_PyUnicode_FromFormat.argtypes = (c_char_p,)
2519+
_PyUnicode_FromFormat.variadic = True
25172520
_PyUnicode_FromFormat.restype = py_object
25182521

25192522
def PyUnicode_FromFormat(format, *args):

Modules/_ctypes/_ctypes.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3320,6 +3320,35 @@ PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
33203320
}
33213321
}
33223322

3323+
static int
3324+
PyCFuncPtr_set_variadic(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
3325+
{
3326+
StgDictObject *dict = PyObject_stgdict((PyObject *)self);
3327+
assert(dict);
3328+
int r = PyObject_IsTrue(ob);
3329+
if (r == 1) {
3330+
dict->flags |= FUNCFLAG_VARIADIC;
3331+
return 0;
3332+
} else if (r == 0) {
3333+
dict->flags &= ~FUNCFLAG_VARIADIC;
3334+
return 0;
3335+
} else {
3336+
return -1;
3337+
}
3338+
}
3339+
3340+
static PyObject *
3341+
PyCFuncPtr_get_variadic(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
3342+
{
3343+
StgDictObject *dict = PyObject_stgdict((PyObject *)self);
3344+
assert(dict); /* Cannot be NULL for PyCFuncPtrObject instances */
3345+
if (dict->flags & FUNCFLAG_VARIADIC)
3346+
Py_RETURN_TRUE;
3347+
else
3348+
Py_RETURN_FALSE;
3349+
}
3350+
3351+
33233352
static int
33243353
PyCFuncPtr_set_argtypes(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
33253354
{
@@ -3365,6 +3394,8 @@ static PyGetSetDef PyCFuncPtr_getsets[] = {
33653394
{ "argtypes", (getter)PyCFuncPtr_get_argtypes,
33663395
(setter)PyCFuncPtr_set_argtypes,
33673396
"specify the argument types", NULL },
3397+
{ "variadic", (getter)PyCFuncPtr_get_variadic, (setter)PyCFuncPtr_set_variadic,
3398+
"specify if function takes variable number of arguments", NULL },
33683399
{ NULL, NULL }
33693400
};
33703401

@@ -5839,6 +5870,7 @@ PyInit__ctypes(void)
58395870
PyModule_AddObject(m, "FUNCFLAG_USE_ERRNO", PyLong_FromLong(FUNCFLAG_USE_ERRNO));
58405871
PyModule_AddObject(m, "FUNCFLAG_USE_LASTERROR", PyLong_FromLong(FUNCFLAG_USE_LASTERROR));
58415872
PyModule_AddObject(m, "FUNCFLAG_PYTHONAPI", PyLong_FromLong(FUNCFLAG_PYTHONAPI));
5873+
PyModule_AddObject(m, "FUNCFLAG_VARIADIC", PyLong_FromLong(FUNCFLAG_VARIADIC));
58425874
PyModule_AddStringConstant(m, "__version__", "1.1.0");
58435875

58445876
PyModule_AddObject(m, "_memmove_addr", PyLong_FromVoidPtr(memmove));

Modules/_ctypes/callproc.c

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@
8686
#define DONT_USE_SEH
8787
#endif
8888

89+
#if defined(__APPLE__) && __arm64__
90+
#define HAVE_FFI_PREP_CIF_VAR 1
91+
#endif
92+
8993
#define CTYPES_CAPSULE_NAME_PYMEM "_ctypes pymem"
9094

9195
static void pymem_destructor(PyObject *ptr)
@@ -812,7 +816,8 @@ static int _call_function_pointer(int flags,
812816
ffi_type **atypes,
813817
ffi_type *restype,
814818
void *resmem,
815-
int argcount)
819+
int argcount,
820+
int argtypecount)
816821
{
817822
PyThreadState *_save = NULL; /* For Py_BLOCK_THREADS and Py_UNBLOCK_THREADS */
818823
PyObject *error_object = NULL;
@@ -835,15 +840,39 @@ static int _call_function_pointer(int flags,
835840
if ((flags & FUNCFLAG_CDECL) == 0)
836841
cc = FFI_STDCALL;
837842
#endif
838-
if (FFI_OK != ffi_prep_cif(&cif,
839-
cc,
840-
argcount,
841-
restype,
842-
atypes)) {
843-
PyErr_SetString(PyExc_RuntimeError,
844-
"ffi_prep_cif failed");
845-
return -1;
843+
844+
#if HAVE_FFI_PREP_CIF_VAR
845+
/* Everyone SHOULD set f.variadic=True on variadic function pointers, but
846+
* lots of existing code will not. If there's at least one arg and more
847+
* args are passed than are defined in the prototype, then it must be a
848+
* variadic function. */
849+
if ((flags & FUNCFLAG_VARIADIC) ||
850+
(argtypecount != 0 && argcount > argtypecount))
851+
{
852+
if (FFI_OK != ffi_prep_cif_var(&cif,
853+
cc,
854+
argtypecount,
855+
argcount,
856+
restype,
857+
atypes)) {
858+
PyErr_SetString(PyExc_RuntimeError,
859+
"ffi_prep_cif_var failed");
860+
return -1;
861+
}
862+
} else {
863+
#endif
864+
if (FFI_OK != ffi_prep_cif(&cif,
865+
cc,
866+
argcount,
867+
restype,
868+
atypes)) {
869+
PyErr_SetString(PyExc_RuntimeError,
870+
"ffi_prep_cif failed");
871+
return -1;
872+
}
873+
#if HAVE_FFI_PREP_CIF_VAR
846874
}
875+
#endif
847876

848877
if (flags & (FUNCFLAG_USE_ERRNO | FUNCFLAG_USE_LASTERROR)) {
849878
error_object = _ctypes_get_errobj(&space);
@@ -1212,9 +1241,8 @@ PyObject *_ctypes_callproc(PPROC pProc,
12121241

12131242
if (-1 == _call_function_pointer(flags, pProc, avalues, atypes,
12141243
rtype, resbuf,
1215-
Py_SAFE_DOWNCAST(argcount,
1216-
Py_ssize_t,
1217-
int)))
1244+
Py_SAFE_DOWNCAST(argcount, Py_ssize_t, int),
1245+
Py_SAFE_DOWNCAST(argtype_count, Py_ssize_t, int)))
12181246
goto cleanup;
12191247

12201248
#ifdef WORDS_BIGENDIAN

Modules/_ctypes/ctypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ PyObject *_ctypes_callproc(PPROC pProc,
285285
#define FUNCFLAG_PYTHONAPI 0x4
286286
#define FUNCFLAG_USE_ERRNO 0x8
287287
#define FUNCFLAG_USE_LASTERROR 0x10
288+
#define FUNCFLAG_VARIADIC 0x20
288289

289290
#define TYPEFLAG_ISPOINTER 0x100
290291
#define TYPEFLAG_HASPOINTER 0x200

0 commit comments

Comments
 (0)