gh-128156: Guard use of ffi_type_complex_double on macOS system libffi (GH-128680)

* Determine ffi complex support at runtime
* Also, generate SIMPLE_TYPE_CHARS once at runtime
This commit is contained in:
Petr Viktorin 2025-01-21 10:59:18 +01:00 committed by GitHub
parent 7239da7559
commit d3b1bb228c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 110 additions and 26 deletions

View file

@ -266,8 +266,8 @@ Fundamental data types
(1)
The constructor accepts any object with a truth value.
Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported, the following
complex types are available:
Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported
in both C and ``libffi``, the following complex types are available:
+----------------------------------+---------------------------------+-----------------+
| ctypes type | C type | Python type |

View file

@ -1,4 +1,5 @@
import unittest
from test.support import MS_WINDOWS
import ctypes
from ctypes import POINTER, c_void_p
@ -150,3 +151,20 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
self.assertIsInstance(POINTER(Sub), p_meta)
self.assertIsSubclass(POINTER(Sub), Sub)
def test_bad_type_message(self):
"""Verify the error message that lists all available type codes"""
# (The string is generated at runtime, so this checks the underlying
# set of types as well as correct construction of the string.)
with self.assertRaises(AttributeError) as cm:
class F(metaclass=PyCSimpleType):
_type_ = "\0"
message = str(cm.exception)
expected_type_chars = list('cbBhHiIlLdCEFfuzZqQPXOv?g')
if not hasattr(ctypes, 'c_float_complex'):
expected_type_chars.remove('C')
expected_type_chars.remove('E')
expected_type_chars.remove('F')
if not MS_WINDOWS:
expected_type_chars.remove('X')
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)

View file

@ -0,0 +1,3 @@
When using macOS system ``libffi``, support for complex types in
:mod:`ctypes` is now checked at runtime (macOS 10.15 or newer). The types
must also be available at build time.

View file

@ -1776,11 +1776,6 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g";
#else
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g";
#endif
/*[clinic input]
_ctypes.c_wchar_p.from_param as c_wchar_p_from_param
@ -2252,17 +2247,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
"which must be a string of length 1");
goto error;
}
if (!strchr(SIMPLE_TYPE_CHARS, *proto_str)) {
fmt = _ctypes_get_fielddesc(proto_str);
if (!fmt) {
PyErr_Format(PyExc_AttributeError,
"class must define a '_type_' attribute which must be\n"
"a single character string containing one of '%s'.",
SIMPLE_TYPE_CHARS);
goto error;
}
fmt = _ctypes_get_fielddesc(proto_str);
if (fmt == NULL) {
PyErr_Format(PyExc_ValueError,
"_type_ '%s' not supported", proto_str);
"a single character string containing one of the\n"
"supported types: '%s'.",
_ctypes_get_simple_type_chars());
goto error;
}

View file

@ -1255,6 +1255,10 @@ for code in 'sbBcdCEFgfhHiIlLqQPzuUZXvO':
// always contains NULLs:
struct fielddesc fmt_nil;
// Result of _ctypes_get_simple_type_chars. Initialized just after
// the rest of formattable, so we stash it here.
char simple_type_chars[26];
};
static struct formattable formattable;
@ -1315,8 +1319,8 @@ _Py_COMP_DIAG_PUSH
/* Delayed initialization. Windows cannot statically reference dynamically
loaded addresses from DLLs. */
void
_ctypes_init_fielddesc(void)
static void
_ctypes_init_fielddesc_locked(void)
{
/* Fixed-width integers */
@ -1432,9 +1436,11 @@ for base_code, base_c_type in [
TABLE_ENTRY_SW(d, &ffi_type_double);
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
TABLE_ENTRY(C, &ffi_type_complex_double);
TABLE_ENTRY(E, &ffi_type_complex_float);
TABLE_ENTRY(F, &ffi_type_complex_longdouble);
if (Py_FFI_COMPLEX_AVAILABLE) {
TABLE_ENTRY(C, &ffi_type_complex_double);
TABLE_ENTRY(E, &ffi_type_complex_float);
TABLE_ENTRY(F, &ffi_type_complex_longdouble);
}
#endif
TABLE_ENTRY(g, &ffi_type_longdouble);
TABLE_ENTRY_SW(f, &ffi_type_float);
@ -1466,21 +1472,75 @@ for base_code, base_c_type in [
formattable.fmt_bool.code = '?';
formattable.fmt_bool.setfunc = bool_set;
formattable.fmt_bool.getfunc = bool_get;
/*[python input]
all_chars = "cbBhHiIlLdCEFfuzZqQPXOv?g"
print(f' assert(sizeof(formattable.simple_type_chars) == {len(all_chars)+1});')
print(f' int i = 0;')
for char in all_chars:
ident_char = {'?': 'bool'}.get(char, char)
print(f" if (formattable.fmt_{ident_char}.code) "
+ f"formattable.simple_type_chars[i++] = '{char}';")
print(f" formattable.simple_type_chars[i] = 0;")
[python start generated code]*/
assert(sizeof(formattable.simple_type_chars) == 26);
int i = 0;
if (formattable.fmt_c.code) formattable.simple_type_chars[i++] = 'c';
if (formattable.fmt_b.code) formattable.simple_type_chars[i++] = 'b';
if (formattable.fmt_B.code) formattable.simple_type_chars[i++] = 'B';
if (formattable.fmt_h.code) formattable.simple_type_chars[i++] = 'h';
if (formattable.fmt_H.code) formattable.simple_type_chars[i++] = 'H';
if (formattable.fmt_i.code) formattable.simple_type_chars[i++] = 'i';
if (formattable.fmt_I.code) formattable.simple_type_chars[i++] = 'I';
if (formattable.fmt_l.code) formattable.simple_type_chars[i++] = 'l';
if (formattable.fmt_L.code) formattable.simple_type_chars[i++] = 'L';
if (formattable.fmt_d.code) formattable.simple_type_chars[i++] = 'd';
if (formattable.fmt_C.code) formattable.simple_type_chars[i++] = 'C';
if (formattable.fmt_E.code) formattable.simple_type_chars[i++] = 'E';
if (formattable.fmt_F.code) formattable.simple_type_chars[i++] = 'F';
if (formattable.fmt_f.code) formattable.simple_type_chars[i++] = 'f';
if (formattable.fmt_u.code) formattable.simple_type_chars[i++] = 'u';
if (formattable.fmt_z.code) formattable.simple_type_chars[i++] = 'z';
if (formattable.fmt_Z.code) formattable.simple_type_chars[i++] = 'Z';
if (formattable.fmt_q.code) formattable.simple_type_chars[i++] = 'q';
if (formattable.fmt_Q.code) formattable.simple_type_chars[i++] = 'Q';
if (formattable.fmt_P.code) formattable.simple_type_chars[i++] = 'P';
if (formattable.fmt_X.code) formattable.simple_type_chars[i++] = 'X';
if (formattable.fmt_O.code) formattable.simple_type_chars[i++] = 'O';
if (formattable.fmt_v.code) formattable.simple_type_chars[i++] = 'v';
if (formattable.fmt_bool.code) formattable.simple_type_chars[i++] = '?';
if (formattable.fmt_g.code) formattable.simple_type_chars[i++] = 'g';
formattable.simple_type_chars[i] = 0;
/*[python end generated code: output=e6e5098a02f4b606 input=72031a625eac00c1]*/
}
#undef FIXINT_FIELDDESC_FOR
_Py_COMP_DIAG_POP
struct fielddesc *
_ctypes_get_fielddesc(const char *fmt)
static void
_ctypes_init_fielddesc(void)
{
static bool initialized = false;
static PyMutex mutex = {0};
PyMutex_Lock(&mutex);
if (!initialized) {
_ctypes_init_fielddesc();
_ctypes_init_fielddesc_locked();
initialized = true;
}
PyMutex_Unlock(&mutex);
}
char *
_ctypes_get_simple_type_chars(void) {
_ctypes_init_fielddesc();
return formattable.simple_type_chars;
}
struct fielddesc *
_ctypes_get_fielddesc(const char *fmt)
{
_ctypes_init_fielddesc();
struct fielddesc *result = NULL;
switch(fmt[0]) {
/*[python input]

View file

@ -5,8 +5,17 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
// Do we support C99 complex types in ffi?
// For Apple's libffi, this must be determined at runtime (see gh-128156).
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
# include "../_complex.h" // complex
# if USING_APPLE_OS_LIBFFI && defined(__has_builtin) && __has_builtin(__builtin_available)
# define Py_FFI_COMPLEX_AVAILABLE __builtin_available(macOS 10.15, *)
# else
# define Py_FFI_COMPLEX_AVAILABLE 1
# endif
#else
# define Py_FFI_COMPLEX_AVAILABLE 0
#endif
#ifndef MS_WIN32
@ -255,6 +264,9 @@ struct fielddesc {
GETFUNC getfunc_swapped;
};
// Get all single-character type codes (for use in error messages)
extern char *_ctypes_get_simple_type_chars(void);
typedef struct CFieldObject {
PyObject_HEAD
Py_ssize_t offset;

View file

@ -407,7 +407,8 @@ Modules/_tkinter.c - trbInCmd -
## other
Include/datetime.h - PyDateTimeAPI -
Modules/_ctypes/cfield.c _ctypes_get_fielddesc initialized -
Modules/_ctypes/cfield.c _ctypes_init_fielddesc initialized -
Modules/_ctypes/cfield.c - formattable -
Modules/_ctypes/malloc_closure.c - _pagesize -
Modules/_cursesmodule.c - curses_module_loaded -
Modules/_cursesmodule.c - curses_initscr_called -
@ -422,7 +423,6 @@ Modules/readline.c - libedit_history_start -
##-----------------------
## state
Modules/_ctypes/cfield.c - formattable -
Modules/_ctypes/malloc_closure.c - free_list -
Modules/_curses_panel.c - lop -
Modules/_ssl/debughelpers.c _PySSL_keylog_callback lock -

Can't render this file because it has a wrong number of fields in line 4.