https://github.com/python/cpython/commit/d3b1bb228c951f8245f36ee28d9b175786522730
commit: d3b1bb228c951f8245f36ee28d9b175786522730
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2025-01-21T10:59:18+01:00
summary:

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

files:
A Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst
M Doc/library/ctypes.rst
M Lib/test/test_ctypes/test_c_simple_type_meta.py
M Modules/_ctypes/_ctypes.c
M Modules/_ctypes/cfield.c
M Modules/_ctypes/ctypes.h
M Tools/c-analyzer/cpython/globals-to-fix.tsv

diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index 164d706e7738e2..615138302e1379 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -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     |
diff --git a/Lib/test/test_ctypes/test_c_simple_type_meta.py 
b/Lib/test/test_ctypes/test_c_simple_type_meta.py
index e8f347a0d0c57b..b446fd5c77dde2 100644
--- a/Lib/test/test_ctypes/test_c_simple_type_meta.py
+++ b/Lib/test/test_ctypes/test_c_simple_type_meta.py
@@ -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 Sub(CtBase):
 
         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)
diff --git 
a/Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst 
b/Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst
new file mode 100644
index 00000000000000..ec6a55040ae6cb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-09-16-20-34.gh-issue-128156.GfObBq.rst
@@ -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.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index c4d130a5ec1d52..5d37610cc1c970 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -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;
     }
 
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index dcac9da75360a4..e045d206f05580 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -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]
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index cc09639e21f7c2..9e8097feae2c17 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -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;
diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv 
b/Tools/c-analyzer/cpython/globals-to-fix.tsv
index a74779803228c2..54954cfb5f83ff 100644
--- a/Tools/c-analyzer/cpython/globals-to-fix.tsv
+++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv
@@ -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    -

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to