Skip to content

Commit d3b1bb2

Browse files
authored
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
1 parent 7239da7 commit d3b1bb2

File tree

7 files changed

+110
-26
lines changed

7 files changed

+110
-26
lines changed

Doc/library/ctypes.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ Fundamental data types
266266
(1)
267267
The constructor accepts any object with a truth value.
268268

269-
Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported, the following
270-
complex types are available:
269+
Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is supported
270+
in both C and ``libffi``, the following complex types are available:
271271

272272
+----------------------------------+---------------------------------+-----------------+
273273
| ctypes type | C type | Python type |

Lib/test/test_ctypes/test_c_simple_type_meta.py

+18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from test.support import MS_WINDOWS
23
import ctypes
34
from ctypes import POINTER, c_void_p
45

@@ -150,3 +151,20 @@ class Sub(CtBase):
150151

151152
self.assertIsInstance(POINTER(Sub), p_meta)
152153
self.assertIsSubclass(POINTER(Sub), Sub)
154+
155+
def test_bad_type_message(self):
156+
"""Verify the error message that lists all available type codes"""
157+
# (The string is generated at runtime, so this checks the underlying
158+
# set of types as well as correct construction of the string.)
159+
with self.assertRaises(AttributeError) as cm:
160+
class F(metaclass=PyCSimpleType):
161+
_type_ = "\0"
162+
message = str(cm.exception)
163+
expected_type_chars = list('cbBhHiIlLdCEFfuzZqQPXOv?g')
164+
if not hasattr(ctypes, 'c_float_complex'):
165+
expected_type_chars.remove('C')
166+
expected_type_chars.remove('E')
167+
expected_type_chars.remove('F')
168+
if not MS_WINDOWS:
169+
expected_type_chars.remove('X')
170+
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
When using macOS system ``libffi``, support for complex types in
2+
:mod:`ctypes` is now checked at runtime (macOS 10.15 or newer). The types
3+
must also be available at build time.

Modules/_ctypes/_ctypes.c

+5-14
Original file line numberDiff line numberDiff line change
@@ -1776,11 +1776,6 @@ class _ctypes.c_void_p "PyObject *" "clinic_state_sub()->PyCSimpleType_Type"
17761776
[clinic start generated code]*/
17771777
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=dd4d9646c56f43a9]*/
17781778

1779-
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
1780-
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdCEFfuzZqQPXOv?g";
1781-
#else
1782-
static const char SIMPLE_TYPE_CHARS[] = "cbBhHiIlLdfuzZqQPXOv?g";
1783-
#endif
17841779

17851780
/*[clinic input]
17861781
_ctypes.c_wchar_p.from_param as c_wchar_p_from_param
@@ -2252,17 +2247,13 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
22522247
"which must be a string of length 1");
22532248
goto error;
22542249
}
2255-
if (!strchr(SIMPLE_TYPE_CHARS, *proto_str)) {
2250+
fmt = _ctypes_get_fielddesc(proto_str);
2251+
if (!fmt) {
22562252
PyErr_Format(PyExc_AttributeError,
22572253
"class must define a '_type_' attribute which must be\n"
2258-
"a single character string containing one of '%s'.",
2259-
SIMPLE_TYPE_CHARS);
2260-
goto error;
2261-
}
2262-
fmt = _ctypes_get_fielddesc(proto_str);
2263-
if (fmt == NULL) {
2264-
PyErr_Format(PyExc_ValueError,
2265-
"_type_ '%s' not supported", proto_str);
2254+
"a single character string containing one of the\n"
2255+
"supported types: '%s'.",
2256+
_ctypes_get_simple_type_chars());
22662257
goto error;
22672258
}
22682259

Modules/_ctypes/cfield.c

+68-8
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,10 @@ for code in 'sbBcdCEFgfhHiIlLqQPzuUZXvO':
12551255

12561256
// always contains NULLs:
12571257
struct fielddesc fmt_nil;
1258+
1259+
// Result of _ctypes_get_simple_type_chars. Initialized just after
1260+
// the rest of formattable, so we stash it here.
1261+
char simple_type_chars[26];
12581262
};
12591263

12601264
static struct formattable formattable;
@@ -1315,8 +1319,8 @@ _Py_COMP_DIAG_PUSH
13151319

13161320
/* Delayed initialization. Windows cannot statically reference dynamically
13171321
loaded addresses from DLLs. */
1318-
void
1319-
_ctypes_init_fielddesc(void)
1322+
static void
1323+
_ctypes_init_fielddesc_locked(void)
13201324
{
13211325
/* Fixed-width integers */
13221326

@@ -1432,9 +1436,11 @@ for base_code, base_c_type in [
14321436

14331437
TABLE_ENTRY_SW(d, &ffi_type_double);
14341438
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
1435-
TABLE_ENTRY(C, &ffi_type_complex_double);
1436-
TABLE_ENTRY(E, &ffi_type_complex_float);
1437-
TABLE_ENTRY(F, &ffi_type_complex_longdouble);
1439+
if (Py_FFI_COMPLEX_AVAILABLE) {
1440+
TABLE_ENTRY(C, &ffi_type_complex_double);
1441+
TABLE_ENTRY(E, &ffi_type_complex_float);
1442+
TABLE_ENTRY(F, &ffi_type_complex_longdouble);
1443+
}
14381444
#endif
14391445
TABLE_ENTRY(g, &ffi_type_longdouble);
14401446
TABLE_ENTRY_SW(f, &ffi_type_float);
@@ -1466,21 +1472,75 @@ for base_code, base_c_type in [
14661472
formattable.fmt_bool.code = '?';
14671473
formattable.fmt_bool.setfunc = bool_set;
14681474
formattable.fmt_bool.getfunc = bool_get;
1475+
1476+
/*[python input]
1477+
all_chars = "cbBhHiIlLdCEFfuzZqQPXOv?g"
1478+
print(f' assert(sizeof(formattable.simple_type_chars) == {len(all_chars)+1});')
1479+
print(f' int i = 0;')
1480+
for char in all_chars:
1481+
ident_char = {'?': 'bool'}.get(char, char)
1482+
print(f" if (formattable.fmt_{ident_char}.code) "
1483+
+ f"formattable.simple_type_chars[i++] = '{char}';")
1484+
print(f" formattable.simple_type_chars[i] = 0;")
1485+
[python start generated code]*/
1486+
assert(sizeof(formattable.simple_type_chars) == 26);
1487+
int i = 0;
1488+
if (formattable.fmt_c.code) formattable.simple_type_chars[i++] = 'c';
1489+
if (formattable.fmt_b.code) formattable.simple_type_chars[i++] = 'b';
1490+
if (formattable.fmt_B.code) formattable.simple_type_chars[i++] = 'B';
1491+
if (formattable.fmt_h.code) formattable.simple_type_chars[i++] = 'h';
1492+
if (formattable.fmt_H.code) formattable.simple_type_chars[i++] = 'H';
1493+
if (formattable.fmt_i.code) formattable.simple_type_chars[i++] = 'i';
1494+
if (formattable.fmt_I.code) formattable.simple_type_chars[i++] = 'I';
1495+
if (formattable.fmt_l.code) formattable.simple_type_chars[i++] = 'l';
1496+
if (formattable.fmt_L.code) formattable.simple_type_chars[i++] = 'L';
1497+
if (formattable.fmt_d.code) formattable.simple_type_chars[i++] = 'd';
1498+
if (formattable.fmt_C.code) formattable.simple_type_chars[i++] = 'C';
1499+
if (formattable.fmt_E.code) formattable.simple_type_chars[i++] = 'E';
1500+
if (formattable.fmt_F.code) formattable.simple_type_chars[i++] = 'F';
1501+
if (formattable.fmt_f.code) formattable.simple_type_chars[i++] = 'f';
1502+
if (formattable.fmt_u.code) formattable.simple_type_chars[i++] = 'u';
1503+
if (formattable.fmt_z.code) formattable.simple_type_chars[i++] = 'z';
1504+
if (formattable.fmt_Z.code) formattable.simple_type_chars[i++] = 'Z';
1505+
if (formattable.fmt_q.code) formattable.simple_type_chars[i++] = 'q';
1506+
if (formattable.fmt_Q.code) formattable.simple_type_chars[i++] = 'Q';
1507+
if (formattable.fmt_P.code) formattable.simple_type_chars[i++] = 'P';
1508+
if (formattable.fmt_X.code) formattable.simple_type_chars[i++] = 'X';
1509+
if (formattable.fmt_O.code) formattable.simple_type_chars[i++] = 'O';
1510+
if (formattable.fmt_v.code) formattable.simple_type_chars[i++] = 'v';
1511+
if (formattable.fmt_bool.code) formattable.simple_type_chars[i++] = '?';
1512+
if (formattable.fmt_g.code) formattable.simple_type_chars[i++] = 'g';
1513+
formattable.simple_type_chars[i] = 0;
1514+
/*[python end generated code: output=e6e5098a02f4b606 input=72031a625eac00c1]*/
1515+
14691516
}
14701517
#undef FIXINT_FIELDDESC_FOR
14711518
_Py_COMP_DIAG_POP
14721519

1473-
struct fielddesc *
1474-
_ctypes_get_fielddesc(const char *fmt)
1520+
static void
1521+
_ctypes_init_fielddesc(void)
14751522
{
14761523
static bool initialized = false;
14771524
static PyMutex mutex = {0};
14781525
PyMutex_Lock(&mutex);
14791526
if (!initialized) {
1480-
_ctypes_init_fielddesc();
1527+
_ctypes_init_fielddesc_locked();
14811528
initialized = true;
14821529
}
14831530
PyMutex_Unlock(&mutex);
1531+
}
1532+
1533+
char *
1534+
_ctypes_get_simple_type_chars(void) {
1535+
_ctypes_init_fielddesc();
1536+
return formattable.simple_type_chars;
1537+
}
1538+
1539+
struct fielddesc *
1540+
_ctypes_get_fielddesc(const char *fmt)
1541+
{
1542+
_ctypes_init_fielddesc();
1543+
14841544
struct fielddesc *result = NULL;
14851545
switch(fmt[0]) {
14861546
/*[python input]

Modules/_ctypes/ctypes.h

+12
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@
55
#include "pycore_moduleobject.h" // _PyModule_GetState()
66
#include "pycore_typeobject.h" // _PyType_GetModuleState()
77

8+
// Do we support C99 complex types in ffi?
9+
// For Apple's libffi, this must be determined at runtime (see gh-128156).
810
#if defined(Py_HAVE_C_COMPLEX) && defined(Py_FFI_SUPPORT_C_COMPLEX)
911
# include "../_complex.h" // complex
12+
# if USING_APPLE_OS_LIBFFI && defined(__has_builtin) && __has_builtin(__builtin_available)
13+
# define Py_FFI_COMPLEX_AVAILABLE __builtin_available(macOS 10.15, *)
14+
# else
15+
# define Py_FFI_COMPLEX_AVAILABLE 1
16+
# endif
17+
#else
18+
# define Py_FFI_COMPLEX_AVAILABLE 0
1019
#endif
1120

1221
#ifndef MS_WIN32
@@ -255,6 +264,9 @@ struct fielddesc {
255264
GETFUNC getfunc_swapped;
256265
};
257266

267+
// Get all single-character type codes (for use in error messages)
268+
extern char *_ctypes_get_simple_type_chars(void);
269+
258270
typedef struct CFieldObject {
259271
PyObject_HEAD
260272
Py_ssize_t offset;

Tools/c-analyzer/cpython/globals-to-fix.tsv

+2-2
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,8 @@ Modules/_tkinter.c - trbInCmd -
407407

408408
## other
409409
Include/datetime.h - PyDateTimeAPI -
410-
Modules/_ctypes/cfield.c _ctypes_get_fielddesc initialized -
410+
Modules/_ctypes/cfield.c _ctypes_init_fielddesc initialized -
411+
Modules/_ctypes/cfield.c - formattable -
411412
Modules/_ctypes/malloc_closure.c - _pagesize -
412413
Modules/_cursesmodule.c - curses_module_loaded -
413414
Modules/_cursesmodule.c - curses_initscr_called -
@@ -422,7 +423,6 @@ Modules/readline.c - libedit_history_start -
422423
##-----------------------
423424
## state
424425

425-
Modules/_ctypes/cfield.c - formattable -
426426
Modules/_ctypes/malloc_closure.c - free_list -
427427
Modules/_curses_panel.c - lop -
428428
Modules/_ssl/debughelpers.c _PySSL_keylog_callback lock -

0 commit comments

Comments
 (0)