Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-41100: ctypes: use the correct ABI for variadic functions #21242

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,13 @@ They are instances of a private class:
value usable as argument (integer, string, ctypes instance). This allows
defining adapters that can adapt custom objects as function parameters.

.. attribute:: variadic

Assign a boolean to specify that the function takes a variable nubmer of
arguments. This does not matter on most platforms, but for Apple arm64
platforms variadic functions have a different calling convention than
normal functions.

.. attribute:: errcheck

Assign a Python function or another callable to this attribute. The
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,8 @@ def test_from_format(self):
c_char_p)

PyBytes_FromFormat = pythonapi.PyBytes_FromFormat
PyBytes_FromFormat.variadic = True
PyBytes_FromFormat.argtypes = (c_char_p,)
PyBytes_FromFormat.restype = py_object

# basic tests
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2509,11 +2509,14 @@ class CAPITest(unittest.TestCase):
def test_from_format(self):
support.import_module('ctypes')
from ctypes import (
c_char_p,
pythonapi, py_object, sizeof,
c_int, c_long, c_longlong, c_ssize_t,
c_uint, c_ulong, c_ulonglong, c_size_t, c_void_p)
name = "PyUnicode_FromFormat"
_PyUnicode_FromFormat = getattr(pythonapi, name)
_PyUnicode_FromFormat.argtypes = (c_char_p,)
_PyUnicode_FromFormat.variadic = True
_PyUnicode_FromFormat.restype = py_object

def PyUnicode_FromFormat(format, *args):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ctypes: Mac OS 11: use correct ABI for variadic functions on arm64
32 changes: 32 additions & 0 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -3320,6 +3320,35 @@ PyCFuncPtr_get_restype(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
}
}

static int
PyCFuncPtr_set_variadic(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
{
StgDictObject *dict = PyObject_stgdict((PyObject *)self);
assert(dict);
int r = PyObject_IsTrue(ob);
if (r == 1) {
dict->flags |= FUNCFLAG_VARIADIC;
return 0;
} else if (r == 0) {
dict->flags &= ~FUNCFLAG_VARIADIC;
return 0;
} else {
return -1;
}
}

static PyObject *
PyCFuncPtr_get_variadic(PyCFuncPtrObject *self, void *Py_UNUSED(ignored))
{
StgDictObject *dict = PyObject_stgdict((PyObject *)self);
assert(dict); /* Cannot be NULL for PyCFuncPtrObject instances */
if (dict->flags & FUNCFLAG_VARIADIC)
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}


static int
PyCFuncPtr_set_argtypes(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ignored))
{
Expand Down Expand Up @@ -3365,6 +3394,8 @@ static PyGetSetDef PyCFuncPtr_getsets[] = {
{ "argtypes", (getter)PyCFuncPtr_get_argtypes,
(setter)PyCFuncPtr_set_argtypes,
"specify the argument types", NULL },
{ "variadic", (getter)PyCFuncPtr_get_variadic, (setter)PyCFuncPtr_set_variadic,
"specify if function takes variable number of arguments", NULL },
{ NULL, NULL }
};

Expand Down Expand Up @@ -5839,6 +5870,7 @@ PyInit__ctypes(void)
PyModule_AddObject(m, "FUNCFLAG_USE_ERRNO", PyLong_FromLong(FUNCFLAG_USE_ERRNO));
PyModule_AddObject(m, "FUNCFLAG_USE_LASTERROR", PyLong_FromLong(FUNCFLAG_USE_LASTERROR));
PyModule_AddObject(m, "FUNCFLAG_PYTHONAPI", PyLong_FromLong(FUNCFLAG_PYTHONAPI));
PyModule_AddObject(m, "FUNCFLAG_VARIADIC", PyLong_FromLong(FUNCFLAG_VARIADIC));
PyModule_AddStringConstant(m, "__version__", "1.1.0");

PyModule_AddObject(m, "_memmove_addr", PyLong_FromVoidPtr(memmove));
Expand Down
52 changes: 40 additions & 12 deletions Modules/_ctypes/callproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@
#define DONT_USE_SEH
#endif

#if defined(__APPLE__) && __arm64__
#define HAVE_FFI_PREP_CIF_VAR 1
#endif

#define CTYPES_CAPSULE_NAME_PYMEM "_ctypes pymem"

static void pymem_destructor(PyObject *ptr)
Expand Down Expand Up @@ -812,7 +816,8 @@ static int _call_function_pointer(int flags,
ffi_type **atypes,
ffi_type *restype,
void *resmem,
int argcount)
int argcount,
int argtypecount)
{
PyThreadState *_save = NULL; /* For Py_BLOCK_THREADS and Py_UNBLOCK_THREADS */
PyObject *error_object = NULL;
Expand All @@ -835,15 +840,39 @@ static int _call_function_pointer(int flags,
if ((flags & FUNCFLAG_CDECL) == 0)
cc = FFI_STDCALL;
#endif
if (FFI_OK != ffi_prep_cif(&cif,
cc,
argcount,
restype,
atypes)) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_prep_cif failed");
return -1;

#if HAVE_FFI_PREP_CIF_VAR
/* Everyone SHOULD set f.variadic=True on variadic function pointers, but
* lots of existing code will not. If there's at least one arg and more
* args are passed than are defined in the prototype, then it must be a
* variadic function. */
if ((flags & FUNCFLAG_VARIADIC) ||
(argtypecount != 0 && argcount > argtypecount))
{
if (FFI_OK != ffi_prep_cif_var(&cif,
cc,
argtypecount,
argcount,
restype,
atypes)) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_prep_cif_var failed");
return -1;
}
} else {
#endif
if (FFI_OK != ffi_prep_cif(&cif,
cc,
argcount,
restype,
atypes)) {
PyErr_SetString(PyExc_RuntimeError,
"ffi_prep_cif failed");
return -1;
}
#if HAVE_FFI_PREP_CIF_VAR
}
#endif

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

if (-1 == _call_function_pointer(flags, pProc, avalues, atypes,
rtype, resbuf,
Py_SAFE_DOWNCAST(argcount,
Py_ssize_t,
int)))
Py_SAFE_DOWNCAST(argcount, Py_ssize_t, int),
Py_SAFE_DOWNCAST(argtype_count, Py_ssize_t, int)))
goto cleanup;

#ifdef WORDS_BIGENDIAN
Expand Down
1 change: 1 addition & 0 deletions Modules/_ctypes/ctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ PyObject *_ctypes_callproc(PPROC pProc,
#define FUNCFLAG_PYTHONAPI 0x4
#define FUNCFLAG_USE_ERRNO 0x8
#define FUNCFLAG_USE_LASTERROR 0x10
#define FUNCFLAG_VARIADIC 0x20

#define TYPEFLAG_ISPOINTER 0x100
#define TYPEFLAG_HASPOINTER 0x200
Expand Down