]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-128156: Guard use of `ffi_type_complex_double` on macOS system libffi (GH-128680)
authorPetr Viktorin <encukou@gmail.com>
Tue, 21 Jan 2025 09:59:18 +0000 (10:59 +0100)
committerGitHub <noreply@github.com>
Tue, 21 Jan 2025 09:59:18 +0000 (10:59 +0100)
* Determine ffi complex support at runtime
* Also, generate SIMPLE_TYPE_CHARS once at runtime

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

index 164d706e7738e226602c20cdadb334833eae576e..615138302e13799461893a18687ec8c81d128035 100644 (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     |
index e8f347a0d0c57b9e3c45511c465487e710736967..b446fd5c77dde29362518a4f8e08943565868ddc 100644 (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)
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 (file)
index 0000000..ec6a550
--- /dev/null
@@ -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.
index c4d130a5ec1d523da07e075e0030b05255141d2d..5d37610cc1c97034cfd652b0a3b34f74052d18a4 100644 (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;
     }
 
index dcac9da75360a4ee4885ce53c4336cd7e570abb3..e045d206f05580957874f78be7a4ca544c6e2805 100644 (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]
index cc09639e21f7c2dedb1eb1f0bb4a28a6370f769a..9e8097feae2c1787000eda593e24524f8bea4cd2 100644 (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;
index a74779803228c2d824898196f72a3de4971c7148..54954cfb5f83ffa46c6dc17c158fd95cf4924af5 100644 (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    -