]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-97588: Move ctypes struct/union layout logic to Python (GH-123352)
authorPetr Viktorin <encukou@gmail.com>
Thu, 5 Sep 2024 09:20:07 +0000 (11:20 +0200)
committerGitHub <noreply@github.com>
Thu, 5 Sep 2024 09:20:07 +0000 (11:20 +0200)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
12 files changed:
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/ctypes/_layout.py [new file with mode: 0644]
Lib/test/test_ctypes/test_bitfields.py
Lib/test/test_ctypes/test_struct_fields.py
Modules/_ctypes/_ctypes.c
Modules/_ctypes/cfield.c
Modules/_ctypes/clinic/cfield.c.h [new file with mode: 0644]
Modules/_ctypes/ctypes.h
Modules/_ctypes/stgdict.c

index d9b46df507dfd79e956ccb380d4c93fd4e83f6d0..6e948e16b7dbe87d114beb9a2111a0f484c7a8f8 100644 (file)
@@ -738,7 +738,6 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_abc_impl));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_abstract_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_active));
-    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_align_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_anonymous_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_argtypes_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_as_parameter_));
@@ -759,21 +758,18 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_initializing));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_io));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_is_text_encoding));
-    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_layout_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_length_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_limbo));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_lock_unlock_module));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_loop));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_needs_com_addref_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_only_immortal));
-    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_pack_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_restype_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_showwarnmsg));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_shutdown));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_slotnames));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime_datetime));
-    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_swappedbytes_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_type_));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_uninitialized_submodules));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_warn_unawaited_coroutine));
@@ -787,6 +783,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(after_in_parent));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(aggregate_class));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(alias));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(align));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(allow_code));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(append));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(arg));
@@ -806,6 +803,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(before));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(big));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(binary_form));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bit_size));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(block));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bound));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(buffer));
@@ -934,6 +932,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fd2));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fdel));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fget));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fields));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(file));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(file_actions));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(filename));
@@ -950,6 +949,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fold));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(follow_symlinks));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(format_spec));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(from_param));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fromlist));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(fromtimestamp));
@@ -986,6 +986,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(importlib));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(in_fd));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(incoming));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(index));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(indexgroup));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(inf));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(infer_variance));
@@ -1006,6 +1007,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(interval));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_struct));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isinstance));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isoformat));
index 10773d7a6c7e3fcbe8efff10455a798674c317aa..5c63a6e519b93d268cf66d1b4a7c288dcdd0e757 100644 (file)
@@ -227,7 +227,6 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(_abc_impl)
         STRUCT_FOR_ID(_abstract_)
         STRUCT_FOR_ID(_active)
-        STRUCT_FOR_ID(_align_)
         STRUCT_FOR_ID(_anonymous_)
         STRUCT_FOR_ID(_argtypes_)
         STRUCT_FOR_ID(_as_parameter_)
@@ -248,21 +247,18 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(_initializing)
         STRUCT_FOR_ID(_io)
         STRUCT_FOR_ID(_is_text_encoding)
-        STRUCT_FOR_ID(_layout_)
         STRUCT_FOR_ID(_length_)
         STRUCT_FOR_ID(_limbo)
         STRUCT_FOR_ID(_lock_unlock_module)
         STRUCT_FOR_ID(_loop)
         STRUCT_FOR_ID(_needs_com_addref_)
         STRUCT_FOR_ID(_only_immortal)
-        STRUCT_FOR_ID(_pack_)
         STRUCT_FOR_ID(_restype_)
         STRUCT_FOR_ID(_showwarnmsg)
         STRUCT_FOR_ID(_shutdown)
         STRUCT_FOR_ID(_slotnames)
         STRUCT_FOR_ID(_strptime)
         STRUCT_FOR_ID(_strptime_datetime)
-        STRUCT_FOR_ID(_swappedbytes_)
         STRUCT_FOR_ID(_type_)
         STRUCT_FOR_ID(_uninitialized_submodules)
         STRUCT_FOR_ID(_warn_unawaited_coroutine)
@@ -276,6 +272,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(after_in_parent)
         STRUCT_FOR_ID(aggregate_class)
         STRUCT_FOR_ID(alias)
+        STRUCT_FOR_ID(align)
         STRUCT_FOR_ID(allow_code)
         STRUCT_FOR_ID(append)
         STRUCT_FOR_ID(arg)
@@ -295,6 +292,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(before)
         STRUCT_FOR_ID(big)
         STRUCT_FOR_ID(binary_form)
+        STRUCT_FOR_ID(bit_size)
         STRUCT_FOR_ID(block)
         STRUCT_FOR_ID(bound)
         STRUCT_FOR_ID(buffer)
@@ -423,6 +421,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(fd2)
         STRUCT_FOR_ID(fdel)
         STRUCT_FOR_ID(fget)
+        STRUCT_FOR_ID(fields)
         STRUCT_FOR_ID(file)
         STRUCT_FOR_ID(file_actions)
         STRUCT_FOR_ID(filename)
@@ -439,6 +438,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(fold)
         STRUCT_FOR_ID(follow_symlinks)
         STRUCT_FOR_ID(format)
+        STRUCT_FOR_ID(format_spec)
         STRUCT_FOR_ID(from_param)
         STRUCT_FOR_ID(fromlist)
         STRUCT_FOR_ID(fromtimestamp)
@@ -475,6 +475,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(importlib)
         STRUCT_FOR_ID(in_fd)
         STRUCT_FOR_ID(incoming)
+        STRUCT_FOR_ID(index)
         STRUCT_FOR_ID(indexgroup)
         STRUCT_FOR_ID(inf)
         STRUCT_FOR_ID(infer_variance)
@@ -495,6 +496,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(intersection)
         STRUCT_FOR_ID(interval)
         STRUCT_FOR_ID(is_running)
+        STRUCT_FOR_ID(is_struct)
         STRUCT_FOR_ID(isatty)
         STRUCT_FOR_ID(isinstance)
         STRUCT_FOR_ID(isoformat)
index 618f8d0a36b6c3460da320f47cf9108d7446b032..bac6b5b8fcfd9d4841820370a9fd067b89cad13f 100644 (file)
@@ -736,7 +736,6 @@ extern "C" {
     INIT_ID(_abc_impl), \
     INIT_ID(_abstract_), \
     INIT_ID(_active), \
-    INIT_ID(_align_), \
     INIT_ID(_anonymous_), \
     INIT_ID(_argtypes_), \
     INIT_ID(_as_parameter_), \
@@ -757,21 +756,18 @@ extern "C" {
     INIT_ID(_initializing), \
     INIT_ID(_io), \
     INIT_ID(_is_text_encoding), \
-    INIT_ID(_layout_), \
     INIT_ID(_length_), \
     INIT_ID(_limbo), \
     INIT_ID(_lock_unlock_module), \
     INIT_ID(_loop), \
     INIT_ID(_needs_com_addref_), \
     INIT_ID(_only_immortal), \
-    INIT_ID(_pack_), \
     INIT_ID(_restype_), \
     INIT_ID(_showwarnmsg), \
     INIT_ID(_shutdown), \
     INIT_ID(_slotnames), \
     INIT_ID(_strptime), \
     INIT_ID(_strptime_datetime), \
-    INIT_ID(_swappedbytes_), \
     INIT_ID(_type_), \
     INIT_ID(_uninitialized_submodules), \
     INIT_ID(_warn_unawaited_coroutine), \
@@ -785,6 +781,7 @@ extern "C" {
     INIT_ID(after_in_parent), \
     INIT_ID(aggregate_class), \
     INIT_ID(alias), \
+    INIT_ID(align), \
     INIT_ID(allow_code), \
     INIT_ID(append), \
     INIT_ID(arg), \
@@ -804,6 +801,7 @@ extern "C" {
     INIT_ID(before), \
     INIT_ID(big), \
     INIT_ID(binary_form), \
+    INIT_ID(bit_size), \
     INIT_ID(block), \
     INIT_ID(bound), \
     INIT_ID(buffer), \
@@ -932,6 +930,7 @@ extern "C" {
     INIT_ID(fd2), \
     INIT_ID(fdel), \
     INIT_ID(fget), \
+    INIT_ID(fields), \
     INIT_ID(file), \
     INIT_ID(file_actions), \
     INIT_ID(filename), \
@@ -948,6 +947,7 @@ extern "C" {
     INIT_ID(fold), \
     INIT_ID(follow_symlinks), \
     INIT_ID(format), \
+    INIT_ID(format_spec), \
     INIT_ID(from_param), \
     INIT_ID(fromlist), \
     INIT_ID(fromtimestamp), \
@@ -984,6 +984,7 @@ extern "C" {
     INIT_ID(importlib), \
     INIT_ID(in_fd), \
     INIT_ID(incoming), \
+    INIT_ID(index), \
     INIT_ID(indexgroup), \
     INIT_ID(inf), \
     INIT_ID(infer_variance), \
@@ -1004,6 +1005,7 @@ extern "C" {
     INIT_ID(intersection), \
     INIT_ID(interval), \
     INIT_ID(is_running), \
+    INIT_ID(is_struct), \
     INIT_ID(isatty), \
     INIT_ID(isinstance), \
     INIT_ID(isoformat), \
index f848a002c3b5d1209f6b3615aad5737f9e0f6bc1..efdbde4c8ea3c661b45b54f84275b7bdf86b191d 100644 (file)
@@ -708,10 +708,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
-    string = &_Py_ID(_align_);
-    _PyUnicode_InternStatic(interp, &string);
-    assert(_PyUnicode_CheckConsistency(string, 1));
-    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(_anonymous_);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -792,10 +788,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
-    string = &_Py_ID(_layout_);
-    _PyUnicode_InternStatic(interp, &string);
-    assert(_PyUnicode_CheckConsistency(string, 1));
-    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(_length_);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -820,10 +812,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
-    string = &_Py_ID(_pack_);
-    _PyUnicode_InternStatic(interp, &string);
-    assert(_PyUnicode_CheckConsistency(string, 1));
-    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(_restype_);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -848,10 +836,6 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
-    string = &_Py_ID(_swappedbytes_);
-    _PyUnicode_InternStatic(interp, &string);
-    assert(_PyUnicode_CheckConsistency(string, 1));
-    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(_type_);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -904,6 +888,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(align);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(allow_code);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -980,6 +968,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(bit_size);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(block);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1492,6 +1484,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(fields);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(file);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1556,6 +1552,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(format_spec);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(from_param);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1700,6 +1700,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(index);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(indexgroup);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -1780,6 +1784,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(is_struct);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(isatty);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Lib/ctypes/_layout.py b/Lib/ctypes/_layout.py
new file mode 100644 (file)
index 0000000..e30db59
--- /dev/null
@@ -0,0 +1,337 @@
+"""Python implementation of computing the layout of a struct/union
+
+This code is internal and tightly coupled to the C part. The interface
+may change at any time.
+"""
+
+import sys
+import warnings
+import struct
+
+from _ctypes import CField, buffer_info
+import ctypes
+
+def round_down(n, multiple):
+    assert n >= 0
+    assert multiple > 0
+    return (n // multiple) * multiple
+
+def round_up(n, multiple):
+    assert n >= 0
+    assert multiple > 0
+    return ((n + multiple - 1) // multiple) * multiple
+
+def LOW_BIT(offset):
+    return offset & 0xFFFF
+
+def NUM_BITS(bitsize):
+    return bitsize >> 16
+
+def BUILD_SIZE(bitsize, offset):
+    assert 0 <= offset, offset
+    assert offset <= 0xFFFF, offset
+    # We don't support zero length bitfields.
+    # And GET_BITFIELD uses NUM_BITS(size) == 0,
+    # to figure out whether we are handling a bitfield.
+    assert bitsize > 0, bitsize
+    result = (bitsize << 16) + offset
+    assert bitsize == NUM_BITS(result), (bitsize, result)
+    assert offset == LOW_BIT(result), (offset, result)
+    return result
+
+def build_size(bit_size, bit_offset, big_endian, type_size):
+    if big_endian:
+        return BUILD_SIZE(bit_size, 8 * type_size - bit_offset - bit_size)
+    return BUILD_SIZE(bit_size, bit_offset)
+
+_INT_MAX = (1 << (ctypes.sizeof(ctypes.c_int) * 8) - 1) - 1
+
+
+class StructUnionLayout:
+    def __init__(self, fields, size, align, format_spec):
+        # sequence of CField objects
+        self.fields = fields
+
+        # total size of the aggregate (rounded up to alignment)
+        self.size = size
+
+        # total alignment requirement of the aggregate
+        self.align = align
+
+        # buffer format specification (as a string, UTF-8 but bes
+        # kept ASCII-only)
+        self.format_spec = format_spec
+
+
+def get_layout(cls, input_fields, is_struct, base):
+    """Return a StructUnionLayout for the given class.
+
+    Called by PyCStructUnionType_update_stginfo when _fields_ is assigned
+    to a class.
+    """
+    # Currently there are two modes, selectable using the '_layout_' attribute:
+    #
+    # 'gcc-sysv' mode places fields one after another, bit by bit.
+    #   But "each bit field must fit within a single object of its specified
+    #   type" (GCC manual, section 15.8 "Bit Field Packing"). When it doesn't,
+    #   we insert a few bits of padding to avoid that.
+    #
+    # 'ms' mode works similar except for bitfield packing.  Adjacent
+    #   bit-fields are packed into the same 1-, 2-, or 4-byte allocation unit
+    #   if the integral types are the same size and if the next bit-field fits
+    #   into the current allocation unit without crossing the boundary imposed
+    #   by the common alignment requirements of the bit-fields.
+    #
+    #   See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mms-bitfields
+    #   for details.
+
+    # We do not support zero length bitfields (we use bitsize != 0
+    # elsewhere to indicate a bitfield). Here, non-bitfields have bit_size
+    # set to size*8.
+
+    # For clarity, variables that count bits have `bit` in their names.
+
+    layout = getattr(cls, '_layout_', None)
+    if layout is None:
+        if sys.platform == 'win32' or getattr(cls, '_pack_', None):
+            gcc_layout = False
+        else:
+            gcc_layout = True
+    elif layout == 'ms':
+        gcc_layout = False
+    elif layout == 'gcc-sysv':
+        gcc_layout = True
+    else:
+        raise ValueError(f'unknown _layout_: {layout!r}')
+
+    align = getattr(cls, '_align_', 1)
+    if align < 0:
+        raise ValueError('_align_ must be a non-negative integer')
+    elif align == 0:
+        # Setting `_align_ = 0` amounts to using the default alignment
+        align == 1
+
+    if base:
+        align = max(ctypes.alignment(base), align)
+
+    swapped_bytes = hasattr(cls, '_swappedbytes_')
+    if swapped_bytes:
+        big_endian = sys.byteorder == 'little'
+    else:
+        big_endian = sys.byteorder == 'big'
+
+    pack = getattr(cls, '_pack_', None)
+    if pack is not None:
+        try:
+            pack = int(pack)
+        except (TypeError, ValueError):
+            raise ValueError("_pack_ must be an integer")
+        if pack < 0:
+            raise ValueError("_pack_ must be a non-negative integer")
+        if pack > _INT_MAX:
+            raise ValueError("_pack_ too big")
+        if gcc_layout:
+            raise ValueError('_pack_ is not compatible with gcc-sysv layout')
+
+    result_fields = []
+
+    if is_struct:
+        format_spec_parts = ["T{"]
+    else:
+        format_spec_parts = ["B"]
+
+    last_field_bit_size = 0  # used in MS layout only
+
+    # `8 * next_byte_offset + next_bit_offset` points to where the
+    # next field would start.
+    next_bit_offset = 0
+    next_byte_offset = 0
+
+    # size if this was a struct (sum of field sizes, plus padding)
+    struct_size = 0
+    # max of field sizes; only meaningful for unions
+    union_size = 0
+
+    if base:
+        struct_size = ctypes.sizeof(base)
+        if gcc_layout:
+            next_bit_offset = struct_size * 8
+        else:
+            next_byte_offset = struct_size
+
+    last_size = struct_size
+    for i, field in enumerate(input_fields):
+        if not is_struct:
+            # Unions start fresh each time
+            last_field_bit_size = 0
+            next_bit_offset = 0
+            next_byte_offset = 0
+
+        # Unpack the field
+        field = tuple(field)
+        try:
+            name, ctype = field
+        except (ValueError, TypeError):
+            try:
+                name, ctype, bit_size = field
+            except (ValueError, TypeError) as exc:
+                raise ValueError(
+                    '_fields_ must be a sequence of (name, C type) pairs '
+                    + 'or (name, C type, bit size) triples') from exc
+            is_bitfield = True
+            if bit_size <= 0:
+                raise ValueError(
+                    f'number of bits invalid for bit field {name!r}')
+            type_size = ctypes.sizeof(ctype)
+            if bit_size > type_size * 8:
+                raise ValueError(
+                    f'number of bits invalid for bit field {name!r}')
+        else:
+            is_bitfield = False
+            type_size = ctypes.sizeof(ctype)
+            bit_size = type_size * 8
+
+        type_bit_size = type_size * 8
+        type_align = ctypes.alignment(ctype) or 1
+        type_bit_align = type_align * 8
+
+        if gcc_layout:
+            # We don't use next_byte_offset here
+            assert pack is None
+            assert next_byte_offset == 0
+
+            # Determine whether the bit field, if placed at the next
+            # free bit, fits within a single object of its specified type.
+            # That is: determine a "slot", sized & aligned for the
+            # specified type, which contains the bitfield's beginning:
+            slot_start_bit = round_down(next_bit_offset, type_bit_align)
+            slot_end_bit = slot_start_bit + type_bit_size
+            # And see if it also contains the bitfield's last bit:
+            field_end_bit = next_bit_offset + bit_size
+            if field_end_bit > slot_end_bit:
+                # It doesn't: add padding (bump up to the next
+                # alignment boundary)
+                next_bit_offset = round_up(next_bit_offset, type_bit_align)
+
+            offset = round_down(next_bit_offset, type_bit_align) // 8
+            if is_bitfield:
+                effective_bit_offset = next_bit_offset - 8 * offset
+                size = build_size(bit_size, effective_bit_offset,
+                                  big_endian, type_size)
+                assert effective_bit_offset <= type_bit_size
+            else:
+                assert offset == next_bit_offset / 8
+                size = type_size
+
+            next_bit_offset += bit_size
+            struct_size = round_up(next_bit_offset, 8) // 8
+        else:
+            if pack:
+                type_align = min(pack, type_align)
+
+            # next_byte_offset points to end of current bitfield.
+            # next_bit_offset is generally non-positive,
+            # and 8 * next_byte_offset + next_bit_offset points just behind
+            # the end of the last field we placed.
+            if (
+                (0 < next_bit_offset + bit_size)
+                or (type_bit_size != last_field_bit_size)
+            ):
+                # Close the previous bitfield (if any)
+                # and start a new bitfield
+                next_byte_offset = round_up(next_byte_offset, type_align)
+
+                next_byte_offset += type_size
+
+                last_field_bit_size = type_bit_size
+                # Reminder: 8 * (next_byte_offset) + next_bit_offset
+                # points to where we would start a new field, namely
+                # just behind where we placed the last field plus an
+                # allowance for alignment.
+                next_bit_offset = -last_field_bit_size
+
+            assert type_bit_size == last_field_bit_size
+
+            offset = next_byte_offset - last_field_bit_size // 8
+            if is_bitfield:
+                assert 0 <= (last_field_bit_size + next_bit_offset)
+                size = build_size(bit_size,
+                                  last_field_bit_size + next_bit_offset,
+                                  big_endian, type_size)
+            else:
+                size = type_size
+            if type_bit_size:
+                assert (last_field_bit_size + next_bit_offset) < type_bit_size
+
+            next_bit_offset += bit_size
+            struct_size = next_byte_offset
+
+        assert (not is_bitfield) or (LOW_BIT(size) <= size * 8)
+
+        # Add the format spec parts
+        if is_struct:
+            padding = offset - last_size
+            format_spec_parts.append(padding_spec(padding))
+
+            fieldfmt, bf_ndim, bf_shape = buffer_info(ctype)
+
+            if bf_shape:
+                format_spec_parts.extend((
+                    "(",
+                    ','.join(str(n) for n in bf_shape),
+                    ")",
+                ))
+
+            if fieldfmt is None:
+                fieldfmt = "B"
+            if isinstance(name, bytes):
+                # a bytes name would be rejected later, but we check early
+                # to avoid a BytesWarning with `python -bb`
+                raise TypeError(
+                    "field {name!r}: name must be a string, not bytes")
+            format_spec_parts.append(f"{fieldfmt}:{name}:")
+
+        result_fields.append(CField(
+            name=name,
+            type=ctype,
+            size=size,
+            offset=offset,
+            bit_size=bit_size if is_bitfield else None,
+            index=i,
+        ))
+        if is_bitfield and not gcc_layout:
+            assert type_bit_size > 0
+
+        align = max(align, type_align)
+        last_size = struct_size
+        if not is_struct:
+            union_size = max(struct_size, union_size)
+
+    if is_struct:
+        total_size = struct_size
+    else:
+        total_size = union_size
+
+    # Adjust the size according to the alignment requirements
+    aligned_size = round_up(total_size, align)
+
+    # Finish up the format spec
+    if is_struct:
+        padding = aligned_size - total_size
+        format_spec_parts.append(padding_spec(padding))
+        format_spec_parts.append("}")
+
+    return StructUnionLayout(
+        fields=result_fields,
+        size=aligned_size,
+        align=align,
+        format_spec="".join(format_spec_parts),
+    )
+
+
+def padding_spec(padding):
+    if padding <= 0:
+        return ""
+    if padding == 1:
+        return "x"
+    return f"{padding}x"
index e6509e6bf89e1db6ba15403924a6ae575193c91f..19ba2f4484e7da374cd56aeb109276e65d27ccb9 100644 (file)
@@ -5,7 +5,9 @@ from ctypes import (CDLL, Structure, sizeof, POINTER, byref, alignment,
                     LittleEndianStructure, BigEndianStructure,
                     c_byte, c_ubyte, c_char, c_char_p, c_void_p, c_wchar,
                     c_uint8, c_uint16, c_uint32, c_uint64,
-                    c_short, c_ushort, c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong)
+                    c_short, c_ushort, c_int, c_uint, c_long, c_ulong,
+                    c_longlong, c_ulonglong,
+                    Union)
 from test import support
 from test.support import import_helper
 _ctypes_test = import_helper.import_module("_ctypes_test")
@@ -186,8 +188,10 @@ class BitFieldTest(unittest.TestCase):
                 self.assertEqual((c_typ, x.a, x.b, x.c), (c_typ, 0, 7, 0))
 
     def fail_fields(self, *fields):
-        return self.get_except(type(Structure), "X", (),
-                               {"_fields_": fields})
+        for layout in "ms", "gcc-sysv":
+            with self.subTest(layout=layout):
+                return self.get_except(type(Structure), "X", (),
+                                       {"_fields_": fields, "layout": layout})
 
     def test_nonint_types(self):
         # bit fields are not allowed on non-integer types.
@@ -204,9 +208,15 @@ class BitFieldTest(unittest.TestCase):
         result = self.fail_fields(("a", c_char, 1))
         self.assertEqual(result, (TypeError, 'bit fields not allowed for type c_char'))
 
-        class Dummy(Structure):
+        class Empty(Structure):
             _fields_ = []
 
+        result = self.fail_fields(("a", Empty, 1))
+        self.assertEqual(result, (ValueError, "number of bits invalid for bit field 'a'"))
+
+        class Dummy(Structure):
+            _fields_ = [("x", c_int)]
+
         result = self.fail_fields(("a", Dummy, 1))
         self.assertEqual(result, (TypeError, 'bit fields not allowed for type Dummy'))
 
@@ -518,6 +528,21 @@ class BitFieldTest(unittest.TestCase):
         x.c = 2
         self.assertEqual(b, b'\xab\xcd\xef\x12')
 
+    def test_union_bitfield(self):
+        class BitfieldUnion(Union):
+            _fields_ = [("a", c_uint32, 1),
+                        ("b", c_uint32, 2),
+                        ("c", c_uint32, 3)]
+        self.assertEqual(sizeof(BitfieldUnion), 4)
+        b = bytearray(4)
+        x = BitfieldUnion.from_buffer(b)
+        x.a = 1
+        self.assertEqual(int.from_bytes(b).bit_count(), 1)
+        x.b = 3
+        self.assertEqual(int.from_bytes(b).bit_count(), 2)
+        x.c = 7
+        self.assertEqual(int.from_bytes(b).bit_count(), 3)
+
 
 if __name__ == "__main__":
     unittest.main()
index 7adab794809def07cf84d9afd0dc0e74291b13cb..7d7a518c0138f9cc0d50b6ff9fc37d5a6d2ba7bc 100644 (file)
@@ -60,7 +60,6 @@ class StructFieldsTestCase(unittest.TestCase):
         self.assertRaises(TypeError, CField)
 
     def test_cfield_type_flags(self):
-        self.assertTrue(CField.__flags__ & Py_TPFLAGS_DISALLOW_INSTANTIATION)
         self.assertTrue(CField.__flags__ & Py_TPFLAGS_IMMUTABLETYPE)
 
     def test_cfield_inheritance_hierarchy(self):
index b55102639c678635d8b13a2a61fd591de1790389..2b23be7b753e34a8383c9224e890c4d77f06ac09 100644 (file)
@@ -320,7 +320,7 @@ _ctypes_alloc_format_string_for_type(char code, int big_endian)
   indicator set.  If called with a suffix of NULL the error indicator must
   already be set.
  */
-char *
+static char *
 _ctypes_alloc_format_string(const char *prefix, const char *suffix)
 {
     size_t len;
@@ -352,7 +352,7 @@ _ctypes_alloc_format_string(const char *prefix, const char *suffix)
   Returns NULL on failure, with the error indicator set.  If called with
   a suffix of NULL the error indicator must already be set.
  */
-char *
+static char *
 _ctypes_alloc_format_string_with_shape(int ndim, const Py_ssize_t *shape,
                                        const char *prefix, const char *suffix)
 {
@@ -664,9 +664,6 @@ StructUnionType_init(PyObject *self, PyObject *args, PyObject *kwds, int isStruc
         Py_DECREF(attrdict);
         return -1;
     }
-    if (!isStruct) {
-        info->flags |= TYPEFLAG_HASUNION;
-    }
 
     info->format = _ctypes_alloc_format_string(NULL, "B");
     if (info->format == NULL) {
@@ -2534,6 +2531,10 @@ converters_from_argtypes(ctypes_state *st, PyObject *ob)
             return -1;
         }
 
+        // TYPEFLAG_HASUNION and TYPEFLAG_HASBITFIELD used to be set
+        // if there were any unions/bitfields;
+        // if the check is re-enabled we either need to loop here or
+        // restore the flag
         if (stginfo != NULL) {
             if (stginfo->flags & TYPEFLAG_HASUNION) {
                 Py_DECREF(converters);
@@ -5780,7 +5781,7 @@ _ctypes_add_types(PyObject *mod)
      * Simple classes
      */
 
-    CREATE_TYPE(st->PyCField_Type, &cfield_spec, NULL, NULL);
+    MOD_ADD_TYPE(st->PyCField_Type, &cfield_spec, NULL, NULL);
 
     /*************************************************
      *
index 2c1fb9b862e12d318e453698241f9d85cf944373..abcab6557de914494eb661ac7c98672bbe27fa7d 100644 (file)
 
 #define CTYPES_CFIELD_CAPSULE_NAME_PYMEM "_ctypes/cfield.c pymem"
 
+/*[clinic input]
+module _ctypes
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=476a19c49b31a75c]*/
+
+#include "clinic/cfield.c.h"
+
 static void pymem_destructor(PyObject *ptr)
 {
     void *p = PyCapsule_GetPointer(ptr, CTYPES_CFIELD_CAPSULE_NAME_PYMEM);
@@ -33,6 +40,10 @@ static void pymem_destructor(PyObject *ptr)
 /*
   PyCField_Type
 */
+/*[clinic input]
+class _ctypes.CField "PyObject *" "PyObject"
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=602817ea3ffc709c]*/
 
 static inline
 Py_ssize_t round_down(Py_ssize_t numToRound, Py_ssize_t multiple)
@@ -61,238 +72,142 @@ Py_ssize_t LOW_BIT(Py_ssize_t offset);
 static inline
 Py_ssize_t BUILD_SIZE(Py_ssize_t bitsize, Py_ssize_t offset);
 
-/* PyCField_FromDesc creates and returns a struct/union field descriptor.
-
-The function expects to be called repeatedly for all fields in a struct or
-union.  It uses helper functions PyCField_FromDesc_gcc and
-PyCField_FromDesc_msvc to simulate the corresponding compilers.
-
-GCC mode places fields one after another, bit by bit.  But "each bit field must
-fit within a single object of its specified type" (GCC manual, section 15.8
-"Bit Field Packing"). When it doesn't, we insert a few bits of padding to
-avoid that.
 
-MSVC mode works similar except for bitfield packing.  Adjacent bit-fields are
-packed into the same 1-, 2-, or 4-byte allocation unit if the integral types
-are the same size and if the next bit-field fits into the current allocation
-unit without crossing the boundary imposed by the common alignment requirements
-of the bit-fields.
+/*[clinic input]
+@classmethod
+_ctypes.CField.__new__ as PyCField_new
 
-See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mms-bitfields for details.
+    name: object(subclass_of='&PyUnicode_Type')
+    type as proto: object
+    size: Py_ssize_t
+    offset: Py_ssize_t
+    index: Py_ssize_t
+    bit_size as bit_size_obj: object = None
 
-We do not support zero length bitfields.  In fact we use bitsize != 0 elsewhere
-to indicate a bitfield. Here, non-bitfields need bitsize set to size*8.
+[clinic start generated code]*/
 
-PyCField_FromDesc manages:
-- *psize: the size of the structure / union so far.
-- *poffset, *pbitofs: 8* (*poffset) + *pbitofs points to where the next field
-  would start.
-- *palign: the alignment requirements of the last field we placed.
-*/
-
-static int
-PyCField_FromDesc_gcc(Py_ssize_t bitsize, Py_ssize_t *pbitofs,
-                Py_ssize_t *psize, Py_ssize_t *poffset, Py_ssize_t *palign,
-                CFieldObject* self, StgInfo* info,
-                int is_bitfield
-                )
-{
-    // We don't use poffset here, so clear it, if it has been set.
-    *pbitofs += *poffset * 8;
-    *poffset = 0;
-
-    *palign = info->align;
-
-    if (bitsize > 0) {
-        // Determine whether the bit field, if placed at the next free bit,
-        // fits within a single object of its specified type.
-        // That is: determine a "slot", sized & aligned for the specified type,
-        // which contains the bitfield's beginning:
-        Py_ssize_t slot_start_bit = round_down(*pbitofs, 8 * info->align);
-        Py_ssize_t slot_end_bit = slot_start_bit + 8 * info->size;
-        // And see if it also contains the bitfield's last bit:
-        Py_ssize_t field_end_bit = *pbitofs + bitsize;
-        if (field_end_bit > slot_end_bit) {
-            // It doesn't: add padding (bump up to the next alignment boundary)
-            *pbitofs = round_up(*pbitofs, 8*info->align);
-        }
-    }
-    assert(*poffset == 0);
-
-    self->offset = round_down(*pbitofs, 8*info->align) / 8;
-    if(is_bitfield) {
-        Py_ssize_t effective_bitsof = *pbitofs - 8 * self->offset;
-        self->size = BUILD_SIZE(bitsize, effective_bitsof);
-        assert(effective_bitsof <= info->size * 8);
-    } else {
-        self->size = info->size;
+static PyObject *
+PyCField_new_impl(PyTypeObject *type, PyObject *name, PyObject *proto,
+                  Py_ssize_t size, Py_ssize_t offset, Py_ssize_t index,
+                  PyObject *bit_size_obj)
+/*[clinic end generated code: output=43649ef9157c5f58 input=3d813f56373c4caa]*/
+{
+    CFieldObject* self = NULL;
+    if (size < 0) {
+        PyErr_Format(PyExc_ValueError,
+                     "size of field %R must not be negative, got %zd",
+                     name, size);
+        goto error;
     }
-
-    *pbitofs += bitsize;
-    *psize = round_up(*pbitofs, 8) / 8;
-
-    return 0;
-}
-
-static int
-PyCField_FromDesc_msvc(
-                Py_ssize_t *pfield_size, Py_ssize_t bitsize,
-                Py_ssize_t *pbitofs, Py_ssize_t *psize, Py_ssize_t *poffset,
-                Py_ssize_t *palign, int pack,
-                CFieldObject* self, StgInfo* info,
-                int is_bitfield
-                )
-{
-    if (pack) {
-        *palign = Py_MIN(pack, info->align);
-    } else {
-        *palign = info->align;
+    // assert: no overflow;
+    if ((unsigned long long int) size
+            >= (1ULL << (8*sizeof(Py_ssize_t)-1)) / 8) {
+        PyErr_Format(PyExc_ValueError,
+                     "size of field %R is too big: %zd", name, size);
+        goto error;
     }
 
-    // *poffset points to end of current bitfield.
-    // *pbitofs is generally non-positive,
-    // and 8 * (*poffset) + *pbitofs points just behind
-    // the end of the last field we placed.
-    if (0 < *pbitofs + bitsize || 8 * info->size != *pfield_size) {
-        // Close the previous bitfield (if any).
-        // and start a new bitfield:
-        *poffset = round_up(*poffset, *palign);
-
-        *poffset += info->size;
-
-        *pfield_size = info->size * 8;
-        // Reminder: 8 * (*poffset) + *pbitofs points to where we would start a
-        // new field.  Ie just behind where we placed the last field plus an
-        // allowance for alignment.
-        *pbitofs = - *pfield_size;
+    PyTypeObject *tp = type;
+    ctypes_state *st = get_module_state_by_class(tp);
+    self = (CFieldObject *)tp->tp_alloc(tp, 0);
+    if (!self) {
+        return NULL;
     }
-
-    assert(8 * info->size == *pfield_size);
-
-    self->offset = *poffset - (*pfield_size) / 8;
-    if(is_bitfield) {
-        assert(0 <= (*pfield_size + *pbitofs));
-        assert((*pfield_size + *pbitofs) < info->size * 8);
-        self->size = BUILD_SIZE(bitsize, *pfield_size + *pbitofs);
+    if (PyUnicode_CheckExact(name)) {
+        self->name = Py_NewRef(name);
     } else {
-        self->size = info->size;
+        self->name = PyObject_Str(name);
+        if (!self->name) {
+            goto error;
+        }
     }
-    assert(*pfield_size + *pbitofs <= info->size * 8);
-
-    *pbitofs += bitsize;
-    *psize = *poffset;
 
-    return 0;
-}
-
-PyObject *
-PyCField_FromDesc(ctypes_state *st, PyObject *desc, Py_ssize_t index,
-                Py_ssize_t *pfield_size, Py_ssize_t bitsize,
-                Py_ssize_t *pbitofs, Py_ssize_t *psize, Py_ssize_t *poffset, Py_ssize_t *palign,
-                int pack, int big_endian, LayoutMode layout_mode)
-{
-    PyTypeObject *tp = st->PyCField_Type;
-    CFieldObject* self = (CFieldObject *)tp->tp_alloc(tp, 0);
-    if (self == NULL) {
-        return NULL;
-    }
     StgInfo *info;
-    if (PyStgInfo_FromType(st, desc, &info) < 0) {
-        Py_DECREF(self);
-        return NULL;
+    if (PyStgInfo_FromType(st, proto, &info) < 0) {
+        goto error;
     }
-    if (!info) {
-        PyErr_SetString(PyExc_TypeError,
-                        "has no _stginfo_");
-        Py_DECREF(self);
-        return NULL;
+    if (info == NULL) {
+        PyErr_Format(PyExc_TypeError,
+                     "type of field %R must be a C type", self->name);
+        goto error;
     }
 
-    PyObject* proto = desc;
+    Py_ssize_t bit_size = NUM_BITS(size);
+    if (bit_size) {
+        assert(bit_size > 0);
+        assert(bit_size <= info->size * 8);
+        switch(info->ffi_type_pointer.type) {
+            case FFI_TYPE_UINT8:
+            case FFI_TYPE_UINT16:
+            case FFI_TYPE_UINT32:
+            case FFI_TYPE_SINT64:
+            case FFI_TYPE_UINT64:
+                break;
+
+            case FFI_TYPE_SINT8:
+            case FFI_TYPE_SINT16:
+            case FFI_TYPE_SINT32:
+                if (info->getfunc != _ctypes_get_fielddesc("c")->getfunc
+                    && info->getfunc != _ctypes_get_fielddesc("u")->getfunc)
+                {
+                    break;
+                }
+                _Py_FALLTHROUGH;  /* else fall through */
+            default:
+                PyErr_Format(PyExc_TypeError,
+                             "bit fields not allowed for type %s",
+                             ((PyTypeObject*)proto)->tp_name);
+                goto error;
+        }
+    }
+
+    self->proto = Py_NewRef(proto);
+    self->size = size;
+    self->offset = offset;
+
+    self->index = index;
 
-    /*  Field descriptors for 'c_char * n' are be scpecial cased to
+    /*  Field descriptors for 'c_char * n' are be special cased to
         return a Python string instead of an Array object instance...
     */
-    SETFUNC setfunc = NULL;
-    GETFUNC getfunc = NULL;
+    self->setfunc = NULL;
+    self->getfunc = NULL;
     if (PyCArrayTypeObject_Check(st, proto)) {
         StgInfo *ainfo;
         if (PyStgInfo_FromType(st, proto, &ainfo) < 0) {
-            Py_DECREF(self);
-            return NULL;
+            goto error;
         }
 
         if (ainfo && ainfo->proto) {
             StgInfo *iinfo;
             if (PyStgInfo_FromType(st, ainfo->proto, &iinfo) < 0) {
-                Py_DECREF(self);
-                return NULL;
+                goto error;
             }
             if (!iinfo) {
                 PyErr_SetString(PyExc_TypeError,
                                 "has no _stginfo_");
-                Py_DECREF(self);
-                return NULL;
+                goto error;
             }
             if (iinfo->getfunc == _ctypes_get_fielddesc("c")->getfunc) {
                 struct fielddesc *fd = _ctypes_get_fielddesc("s");
-                getfunc = fd->getfunc;
-                setfunc = fd->setfunc;
+                self->getfunc = fd->getfunc;
+                self->setfunc = fd->setfunc;
             }
             if (iinfo->getfunc == _ctypes_get_fielddesc("u")->getfunc) {
                 struct fielddesc *fd = _ctypes_get_fielddesc("U");
-                getfunc = fd->getfunc;
-                setfunc = fd->setfunc;
+                self->getfunc = fd->getfunc;
+                self->setfunc = fd->setfunc;
             }
         }
     }
 
-    self->setfunc = setfunc;
-    self->getfunc = getfunc;
-    self->index = index;
-
-    self->proto = Py_NewRef(proto);
-
-    int is_bitfield = !!bitsize;
-    if(!is_bitfield) {
-        assert(info->size >= 0);
-        // assert: no overflow;
-        assert((unsigned long long int) info->size
-            < (1ULL << (8*sizeof(Py_ssize_t)-1)) / 8);
-        bitsize = 8 * info->size;
-        // Caution: bitsize might still be 0 now.
-    }
-    assert(bitsize <= info->size * 8);
-
-    int result;
-    if (layout_mode == LAYOUT_MODE_MS) {
-        result = PyCField_FromDesc_msvc(
-                pfield_size, bitsize, pbitofs,
-                psize, poffset, palign,
-                pack,
-                self, info,
-                is_bitfield
-                );
-    } else {
-        assert(pack == 0);
-        result = PyCField_FromDesc_gcc(
-                bitsize, pbitofs,
-                psize, poffset, palign,
-                self, info,
-                is_bitfield
-                );
-    }
-    if (result < 0) {
-        Py_DECREF(self);
-        return NULL;
-    }
-    assert(!is_bitfield || (LOW_BIT(self->size) <= self->size * 8));
-    if(big_endian && is_bitfield) {
-        self->size = BUILD_SIZE(NUM_BITS(self->size), 8*info->size - LOW_BIT(self->size) - bitsize);
-    }
     return (PyObject *)self;
+error:
+    Py_XDECREF(self);
+    return NULL;
 }
 
+
 static int
 PyCField_set(CFieldObject *self, PyObject *inst, PyObject *value)
 {
@@ -371,8 +286,10 @@ PyCField_dealloc(PyObject *self)
 {
     PyTypeObject *tp = Py_TYPE(self);
     PyObject_GC_UnTrack(self);
-    (void)PyCField_clear((CFieldObject *)self);
-    Py_TYPE(self)->tp_free((PyObject *)self);
+    CFieldObject *self_cf = (CFieldObject *)self;
+    (void)PyCField_clear(self_cf);
+    Py_CLEAR(self_cf->name);
+    Py_TYPE(self)->tp_free(self);
     Py_DECREF(tp);
 }
 
@@ -398,6 +315,7 @@ PyCField_repr(CFieldObject *self)
 }
 
 static PyType_Slot cfield_slots[] = {
+    {Py_tp_new, PyCField_new},
     {Py_tp_dealloc, PyCField_dealloc},
     {Py_tp_repr, PyCField_repr},
     {Py_tp_doc, (void *)PyDoc_STR("Structure/Union member")},
@@ -413,7 +331,7 @@ PyType_Spec cfield_spec = {
     .name = "_ctypes.CField",
     .basicsize = sizeof(CFieldObject),
     .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
-              Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION),
+              Py_TPFLAGS_IMMUTABLETYPE),
     .slots = cfield_slots,
 };
 
diff --git a/Modules/_ctypes/clinic/cfield.c.h b/Modules/_ctypes/clinic/cfield.c.h
new file mode 100644 (file)
index 0000000..df5da78
--- /dev/null
@@ -0,0 +1,113 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+#  include "pycore_gc.h"          // PyGC_Head
+#  include "pycore_runtime.h"     // _Py_ID()
+#endif
+#include "pycore_abstract.h"      // _PyNumber_Index()
+#include "pycore_modsupport.h"    // _PyArg_UnpackKeywords()
+
+static PyObject *
+PyCField_new_impl(PyTypeObject *type, PyObject *name, PyObject *proto,
+                  Py_ssize_t size, Py_ssize_t offset, Py_ssize_t index,
+                  PyObject *bit_size_obj);
+
+static PyObject *
+PyCField_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    PyObject *return_value = NULL;
+    #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+    #define NUM_KEYWORDS 6
+    static struct {
+        PyGC_Head _this_is_not_used;
+        PyObject_VAR_HEAD
+        PyObject *ob_item[NUM_KEYWORDS];
+    } _kwtuple = {
+        .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+        .ob_item = { &_Py_ID(name), &_Py_ID(type), &_Py_ID(size), &_Py_ID(offset), &_Py_ID(index), &_Py_ID(bit_size), },
+    };
+    #undef NUM_KEYWORDS
+    #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+    #else  // !Py_BUILD_CORE
+    #  define KWTUPLE NULL
+    #endif  // !Py_BUILD_CORE
+
+    static const char * const _keywords[] = {"name", "type", "size", "offset", "index", "bit_size", NULL};
+    static _PyArg_Parser _parser = {
+        .keywords = _keywords,
+        .fname = "CField",
+        .kwtuple = KWTUPLE,
+    };
+    #undef KWTUPLE
+    PyObject *argsbuf[6];
+    PyObject * const *fastargs;
+    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
+    Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 5;
+    PyObject *name;
+    PyObject *proto;
+    Py_ssize_t size;
+    Py_ssize_t offset;
+    Py_ssize_t index;
+    PyObject *bit_size_obj = Py_None;
+
+    fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 5, 6, 0, argsbuf);
+    if (!fastargs) {
+        goto exit;
+    }
+    if (!PyUnicode_Check(fastargs[0])) {
+        _PyArg_BadArgument("CField", "argument 'name'", "str", fastargs[0]);
+        goto exit;
+    }
+    name = fastargs[0];
+    proto = fastargs[1];
+    {
+        Py_ssize_t ival = -1;
+        PyObject *iobj = _PyNumber_Index(fastargs[2]);
+        if (iobj != NULL) {
+            ival = PyLong_AsSsize_t(iobj);
+            Py_DECREF(iobj);
+        }
+        if (ival == -1 && PyErr_Occurred()) {
+            goto exit;
+        }
+        size = ival;
+    }
+    {
+        Py_ssize_t ival = -1;
+        PyObject *iobj = _PyNumber_Index(fastargs[3]);
+        if (iobj != NULL) {
+            ival = PyLong_AsSsize_t(iobj);
+            Py_DECREF(iobj);
+        }
+        if (ival == -1 && PyErr_Occurred()) {
+            goto exit;
+        }
+        offset = ival;
+    }
+    {
+        Py_ssize_t ival = -1;
+        PyObject *iobj = _PyNumber_Index(fastargs[4]);
+        if (iobj != NULL) {
+            ival = PyLong_AsSsize_t(iobj);
+            Py_DECREF(iobj);
+        }
+        if (ival == -1 && PyErr_Occurred()) {
+            goto exit;
+        }
+        index = ival;
+    }
+    if (!noptargs) {
+        goto skip_optional_pos;
+    }
+    bit_size_obj = fastargs[5];
+skip_optional_pos:
+    return_value = PyCField_new_impl(type, name, proto, size, offset, index, bit_size_obj);
+
+exit:
+    return return_value;
+}
+/*[clinic end generated code: output=27c010bae9be7213 input=a9049054013a1b77]*/
index a794cfe86b5f4272a6a0cb367532b17c82bfcf8d..2eb1b6cae4d81bbdf0d5f2e0be3477148b51f8d7 100644 (file)
@@ -216,18 +216,6 @@ extern int PyObject_stginfo(PyObject *self, Py_ssize_t *psize, Py_ssize_t *palig
 
 extern struct fielddesc *_ctypes_get_fielddesc(const char *fmt);
 
-typedef enum {
-    LAYOUT_MODE_MS,
-    LAYOUT_MODE_GCC_SYSV,
-} LayoutMode;
-
-extern PyObject *
-PyCField_FromDesc(ctypes_state *st, PyObject *desc, Py_ssize_t index,
-                Py_ssize_t *pfield_size, Py_ssize_t bitsize,
-                Py_ssize_t *pbitofs, Py_ssize_t *psize, Py_ssize_t *poffset,
-                Py_ssize_t *palign,
-                int pack, int is_big_endian, LayoutMode layout_mode);
-
 extern PyObject *PyCData_AtAddress(ctypes_state *st, PyObject *type, void *buf);
 extern PyObject *PyCData_FromBytes(ctypes_state *st, PyObject *type, char *data, Py_ssize_t length);
 
@@ -259,16 +247,18 @@ struct fielddesc {
     GETFUNC getfunc_swapped;
 };
 
-typedef struct {
+typedef struct CFieldObject {
     PyObject_HEAD
     Py_ssize_t offset;
     Py_ssize_t size;
     Py_ssize_t index;                   /* Index into CDataObject's
                                        object array */
-    PyObject *proto;                    /* a type or NULL */
+    PyObject *proto;                    /* underlying ctype; must have StgInfo */
     GETFUNC getfunc;                    /* getter function if proto is NULL */
     SETFUNC setfunc;                    /* setter function if proto is NULL */
     int anonymous;
+
+    PyObject *name;                     /* exact PyUnicode */
 } CFieldObject;
 
 /****************************************************************
@@ -379,8 +369,6 @@ PyObject *_ctypes_callproc(ctypes_state *st,
 
 #define TYPEFLAG_ISPOINTER 0x100
 #define TYPEFLAG_HASPOINTER 0x200
-#define TYPEFLAG_HASUNION 0x400
-#define TYPEFLAG_HASBITFIELD 0x800
 
 #define DICTFLAG_FINAL 0x1000
 
@@ -436,10 +424,6 @@ extern void *_ctypes_alloc_closure(void);
 
 extern PyObject *PyCData_FromBaseObj(ctypes_state *st, PyObject *type,
                                      PyObject *base, Py_ssize_t index, char *adr);
-extern char *_ctypes_alloc_format_string(const char *prefix, const char *suffix);
-extern char *_ctypes_alloc_format_string_with_shape(int ndim,
-                                                const Py_ssize_t *shape,
-                                                const char *prefix, const char *suffix);
 
 extern int _ctypes_simple_instance(ctypes_state *st, PyObject *obj);
 
index 970f0a033fbb0b6849ace7b1a043d2811ee9e025..c9cd1c6e7381f6932fa5096dd47c14409fb61262 100644 (file)
@@ -210,29 +210,6 @@ MakeAnonFields(PyObject *type)
     return 0;
 }
 
-/*
-  Allocate a memory block for a pep3118 format string, copy prefix (if
-  non-null) into it and append `{padding}x` to the end.
-  Returns NULL on failure, with the error indicator set.
-*/
-char *
-_ctypes_alloc_format_padding(const char *prefix, Py_ssize_t padding)
-{
-    /* int64 decimal characters + x + null */
-    char buf[19 + 1 + 1];
-
-    assert(padding > 0);
-
-    if (padding == 1) {
-        /* Use x instead of 1x, for brevity */
-        return _ctypes_alloc_format_string(prefix, "x");
-    }
-
-    int ret = PyOS_snprintf(buf, sizeof(buf), "%zdx", padding); (void)ret;
-    assert(0 <= ret && ret < (Py_ssize_t)sizeof(buf));
-    return _ctypes_alloc_format_string(prefix, buf);
-}
-
 /*
   Retrieve the (optional) _pack_ attribute from a type, the _fields_ attribute,
   and initialize StgInfo.  Used for Structure and Union subclasses.
@@ -240,125 +217,35 @@ _ctypes_alloc_format_padding(const char *prefix, Py_ssize_t padding)
 int
 PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct)
 {
-    Py_ssize_t len, offset, size, align, i;
-    Py_ssize_t union_size, total_align, aligned_size;
-    Py_ssize_t field_size = 0;
-    Py_ssize_t bitofs = 0;
     PyObject *tmp;
-    int pack;
-    int forced_alignment = 1;
     Py_ssize_t ffi_ofs;
-    int big_endian;
     int arrays_seen = 0;
 
-    if (fields == NULL)
-        return 0;
-
-    int rc = PyObject_HasAttrWithError(type, &_Py_ID(_swappedbytes_));
-    if (rc < 0) {
-        return -1;
-    }
-    if (rc) {
-        big_endian = !PY_BIG_ENDIAN;
-    }
-    else {
-        big_endian = PY_BIG_ENDIAN;
-    }
-
-    if (PyObject_GetOptionalAttr(type, &_Py_ID(_pack_), &tmp) < 0) {
-        return -1;
-    }
-    if (tmp) {
-        pack = PyLong_AsInt(tmp);
-        Py_DECREF(tmp);
-        if (pack < 0) {
-            if (!PyErr_Occurred() ||
-                PyErr_ExceptionMatches(PyExc_TypeError) ||
-                PyErr_ExceptionMatches(PyExc_OverflowError))
-            {
-                PyErr_SetString(PyExc_ValueError,
-                                "_pack_ must be a non-negative integer");
-            }
-            return -1;
-        }
-    }
-    else {
-        /* Setting `_pack_ = 0` amounts to using the default alignment */
-        pack = 0;
-    }
-
-    #ifdef MS_WIN32
-    LayoutMode layout_mode = LAYOUT_MODE_MS;
-    #else
-    LayoutMode layout_mode = (pack > 0) ? LAYOUT_MODE_MS : LAYOUT_MODE_GCC_SYSV;
-    #endif
-
-    if (PyObject_GetOptionalAttr(type, &_Py_ID(_layout_), &tmp) < 0) {
-        return -1;
-    }
-    if (tmp) {
-        if (!PyUnicode_Check(tmp)) {
-            PyErr_SetString(PyExc_TypeError,
-                            "_layout_ must be a string");
-            return -1;
-        }
-        if (PyUnicode_CompareWithASCIIString(tmp, "ms") == 0) {
-            layout_mode = LAYOUT_MODE_MS;
-        }
-        else if (PyUnicode_CompareWithASCIIString(tmp, "gcc-sysv") == 0) {
-            layout_mode = LAYOUT_MODE_GCC_SYSV;
-            if (pack > 0) {
-                PyErr_SetString(PyExc_ValueError,
-                                "_pack_ is not compatible with _layout_=\"gcc-sysv\"");
-                return -1;
-            }
-        }
-        else {
-            PyErr_Format(PyExc_ValueError,
-                            "unknown _layout_ %R", tmp);
-            return -1;
-        }
-    }
-    if (PyObject_GetOptionalAttr(type, &_Py_ID(_align_), &tmp) < 0) {
-        return -1;
-    }
-    if (tmp) {
-        forced_alignment = PyLong_AsInt(tmp);
-        Py_DECREF(tmp);
-        if (forced_alignment < 0) {
-            if (!PyErr_Occurred() ||
-                PyErr_ExceptionMatches(PyExc_TypeError) ||
-                PyErr_ExceptionMatches(PyExc_OverflowError))
-            {
-                PyErr_SetString(PyExc_ValueError,
-                                "_align_ must be a non-negative integer");
-            }
-            return -1;
-        }
-    }
-    else {
-        /* Setting `_align_ = 0` amounts to using the default alignment */
-        forced_alignment = 1;
-    }
+    int retval = -1;
+    // The following are NULL or hold strong references.
+    // They're cleared on error.
+    PyObject *layout_fields = NULL;
+    PyObject *layout = NULL;
+    PyObject *format_spec_obj = NULL;
 
-    len = PySequence_Size(fields);
-    if (len == -1) {
-        if (PyErr_ExceptionMatches(PyExc_TypeError)) {
-            PyErr_SetString(PyExc_TypeError,
-                            "'_fields_' must be a sequence of pairs");
-        }
-        return -1;
+    if (fields == NULL) {
+        return 0;
     }
 
     ctypes_state *st = get_module_state_by_def(Py_TYPE(type));
     StgInfo *stginfo;
     if (PyStgInfo_FromType(st, type, &stginfo) < 0) {
-        return -1;
+        goto error;
     }
     if (!stginfo) {
         PyErr_SetString(PyExc_TypeError,
                         "ctypes state is not initialized");
-        return -1;
+        goto error;
+    }
+    PyObject *base = (PyObject *)((PyTypeObject *)type)->tp_base;
+    StgInfo *baseinfo;
+    if (PyStgInfo_FromType(st, base, &baseinfo) < 0) {
+        goto error;
     }
 
     /* If this structure/union is already marked final we cannot assign
@@ -367,40 +254,114 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
     if (stginfo->flags & DICTFLAG_FINAL) {/* is final ? */
         PyErr_SetString(PyExc_AttributeError,
                         "_fields_ is final");
-        return -1;
+        goto error;
+    }
+
+    PyObject *layout_func = _PyImport_GetModuleAttrString("ctypes._layout",
+                                                          "get_layout");
+    if (!layout_func) {
+        goto error;
+    }
+    PyObject *kwnames = PyTuple_Pack(
+        2,
+        &_Py_ID(is_struct),
+        &_Py_ID(base));
+    if (!kwnames) {
+        goto error;
+    }
+    layout = PyObject_Vectorcall(
+        layout_func,
+        1 + (PyObject*[]){
+            NULL,
+            /* positional args */
+            type,
+            fields,
+            /* keyword args */
+            isStruct ? Py_True : Py_False,
+            baseinfo ? base : Py_None},
+        2 | PY_VECTORCALL_ARGUMENTS_OFFSET,
+        kwnames);
+    Py_DECREF(kwnames);
+    Py_DECREF(layout_func);
+    fields = NULL; // a borrowed reference we won't be using again
+    if (!layout) {
+        goto error;
+    }
+
+    tmp = PyObject_GetAttr(layout, &_Py_ID(align));
+    if (!tmp) {
+        goto error;
+    }
+    Py_ssize_t total_align = PyLong_AsInt(tmp);
+    Py_DECREF(tmp);
+    if (total_align < 0) {
+        if (!PyErr_Occurred()) {
+            PyErr_SetString(PyExc_ValueError,
+                            "align must be a non-negative integer");
+        }
+        goto error;
+    }
+
+    tmp = PyObject_GetAttr(layout, &_Py_ID(size));
+    if (!tmp) {
+        goto error;
+    }
+    Py_ssize_t total_size = PyLong_AsInt(tmp);
+    Py_DECREF(tmp);
+    if (total_size < 0) {
+        if (!PyErr_Occurred()) {
+            PyErr_SetString(PyExc_ValueError,
+                            "size must be a non-negative integer");
+        }
+        goto error;
+    }
+
+    format_spec_obj = PyObject_GetAttr(layout, &_Py_ID(format_spec));
+    if (!format_spec_obj) {
+        goto error;
+    }
+    Py_ssize_t format_spec_size;
+    const char *format_spec = PyUnicode_AsUTF8AndSize(format_spec_obj,
+                                                      &format_spec_size);
+    if (!format_spec) {
+        goto error;
     }
 
     if (stginfo->format) {
         PyMem_Free(stginfo->format);
         stginfo->format = NULL;
     }
+    stginfo->format = PyMem_Malloc(format_spec_size + 1);
+    if (!stginfo->format) {
+        PyErr_NoMemory();
+        goto error;
+    }
+    memcpy(stginfo->format, format_spec, format_spec_size + 1);
 
-    if (stginfo->ffi_type_pointer.elements)
-        PyMem_Free(stginfo->ffi_type_pointer.elements);
-
-    StgInfo *baseinfo;
-    if (PyStgInfo_FromType(st, (PyObject *)((PyTypeObject *)type)->tp_base,
-                           &baseinfo) < 0) {
-        return -1;
+    PyObject *layout_fields_obj = PyObject_GetAttr(layout, &_Py_ID(fields));
+    if (!layout_fields_obj) {
+        goto error;
     }
-    if (baseinfo) {
-        stginfo->flags |= (baseinfo->flags &
-                           (TYPEFLAG_HASUNION | TYPEFLAG_HASBITFIELD));
+    layout_fields = PySequence_Tuple(layout_fields_obj);
+    Py_DECREF(layout_fields_obj);
+    if (!layout_fields) {
+        goto error;
     }
-    if (!isStruct) {
-        stginfo->flags |= TYPEFLAG_HASUNION;
+    Py_CLEAR(layout);
+
+    Py_ssize_t len = PyTuple_GET_SIZE(layout_fields);
+
+    if (stginfo->ffi_type_pointer.elements) {
+        PyMem_Free(stginfo->ffi_type_pointer.elements);
+        stginfo->ffi_type_pointer.elements = NULL;
     }
+
     if (baseinfo) {
-        size = offset = baseinfo->size;
-        align = baseinfo->align;
-        union_size = 0;
-        total_align = align ? align : 1;
-        total_align = max(total_align, forced_alignment);
         stginfo->ffi_type_pointer.type = FFI_TYPE_STRUCT;
         stginfo->ffi_type_pointer.elements = PyMem_New(ffi_type *, baseinfo->length + len + 1);
         if (stginfo->ffi_type_pointer.elements == NULL) {
             PyErr_NoMemory();
-            return -1;
+            goto error;
         }
         memset(stginfo->ffi_type_pointer.elements, 0,
                sizeof(ffi_type *) * (baseinfo->length + len + 1));
@@ -411,231 +372,61 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
         }
         ffi_ofs = baseinfo->length;
     } else {
-        offset = 0;
-        size = 0;
-        align = 0;
-        union_size = 0;
-        total_align = forced_alignment;
         stginfo->ffi_type_pointer.type = FFI_TYPE_STRUCT;
         stginfo->ffi_type_pointer.elements = PyMem_New(ffi_type *, len + 1);
         if (stginfo->ffi_type_pointer.elements == NULL) {
             PyErr_NoMemory();
-            return -1;
+            goto error;
         }
         memset(stginfo->ffi_type_pointer.elements, 0,
                sizeof(ffi_type *) * (len + 1));
         ffi_ofs = 0;
     }
 
-    assert(stginfo->format == NULL);
-    if (isStruct) {
-        stginfo->format = _ctypes_alloc_format_string(NULL, "T{");
-    } else {
-        /* PEP3118 doesn't support union. Use 'B' for bytes. */
-        stginfo->format = _ctypes_alloc_format_string(NULL, "B");
-    }
-    if (stginfo->format == NULL)
-        return -1;
+    for (Py_ssize_t i = 0; i < len; ++i) {
+        PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i);
+        assert(prop_obj);
+        if (!PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type)) {
+            PyErr_Format(PyExc_TypeError,
+                         "fields must be of type CField, got %T", prop_obj);
+            goto error;
 
-    for (i = 0; i < len; ++i) {
-        PyObject *name = NULL, *desc = NULL;
-        PyObject *pair = PySequence_GetItem(fields, i);
-        PyObject *prop;
-        Py_ssize_t bitsize = 0;
+        }
+        CFieldObject *prop = (CFieldObject *)prop_obj; // borrow from prop_obj
 
-        if (!pair || !PyArg_ParseTuple(pair, "UO|n", &name, &desc, &bitsize)) {
-            PyErr_SetString(PyExc_TypeError,
-                            "'_fields_' must be a sequence of (name, C type) pairs");
-            Py_XDECREF(pair);
-            return -1;
+        if (prop->index != i) {
+            PyErr_Format(PyExc_ValueError,
+                         "field %R index mismatch (expected %zd, got %zd)",
+                         prop->name, i, prop->index);
+            goto error;
         }
-        if (PyCArrayTypeObject_Check(st, desc)) {
+
+        if (PyCArrayTypeObject_Check(st, prop->proto)) {
             arrays_seen = 1;
         }
 
         StgInfo *info;
-        if (PyStgInfo_FromType(st, desc, &info) < 0) {
-            Py_DECREF(pair);
-            return -1;
-        }
-        if (info == NULL) {
-            Py_DECREF(pair);
-            PyErr_Format(PyExc_TypeError,
-                         "second item in _fields_ tuple (index %zd) must be a C type",
-                         i);
-            return -1;
+        if (PyStgInfo_FromType(st, prop->proto, &info) < 0) {
+            goto error;
         }
+        assert(info);
 
         stginfo->ffi_type_pointer.elements[ffi_ofs + i] = &info->ffi_type_pointer;
         if (info->flags & (TYPEFLAG_ISPOINTER | TYPEFLAG_HASPOINTER))
             stginfo->flags |= TYPEFLAG_HASPOINTER;
-        stginfo->flags |= info->flags & (TYPEFLAG_HASUNION | TYPEFLAG_HASBITFIELD);
         info->flags |= DICTFLAG_FINAL; /* mark field type final */
-        if (PyTuple_Size(pair) == 3) { /* bits specified */
-            stginfo->flags |= TYPEFLAG_HASBITFIELD;
-            switch(info->ffi_type_pointer.type) {
-            case FFI_TYPE_UINT8:
-            case FFI_TYPE_UINT16:
-            case FFI_TYPE_UINT32:
-            case FFI_TYPE_SINT64:
-            case FFI_TYPE_UINT64:
-                break;
-
-            case FFI_TYPE_SINT8:
-            case FFI_TYPE_SINT16:
-            case FFI_TYPE_SINT32:
-                if (info->getfunc != _ctypes_get_fielddesc("c")->getfunc
-                    && info->getfunc != _ctypes_get_fielddesc("u")->getfunc)
-                {
-                    break;
-                }
-                _Py_FALLTHROUGH;  /* else fall through */
-            default:
-                PyErr_Format(PyExc_TypeError,
-                             "bit fields not allowed for type %s",
-                             ((PyTypeObject *)desc)->tp_name);
-                Py_DECREF(pair);
-                return -1;
-            }
-            if (bitsize <= 0 || bitsize > info->size * 8) {
-                PyErr_Format(PyExc_ValueError,
-                                "number of bits invalid for bit field %R",
-                                name);
-                Py_DECREF(pair);
-                return -1;
-            }
-        } else
-            bitsize = 0;
-
-        if (isStruct) {
-            const char *fieldfmt = info->format ? info->format : "B";
-            const char *fieldname = PyUnicode_AsUTF8(name);
-            char *ptr;
-            Py_ssize_t len;
-            char *buf;
-            Py_ssize_t last_size = size;
-            Py_ssize_t padding;
-
-            if (fieldname == NULL)
-            {
-                Py_DECREF(pair);
-                return -1;
-            }
-
-            /* construct the field now, as `prop->offset` is `offset` with
-               corrected alignment */
-            prop = PyCField_FromDesc(st, desc, i,
-                                   &field_size, bitsize, &bitofs,
-                                   &size, &offset, &align,
-                                   pack, big_endian, layout_mode);
-            if (prop == NULL) {
-                Py_DECREF(pair);
-                return -1;
-            }
-
-            /* number of bytes between the end of the last field and the start
-               of this one */
-            padding = ((CFieldObject *)prop)->offset - last_size;
-
-            if (padding > 0) {
-                ptr = stginfo->format;
-                stginfo->format = _ctypes_alloc_format_padding(ptr, padding);
-                PyMem_Free(ptr);
-                if (stginfo->format == NULL) {
-                    Py_DECREF(pair);
-                    Py_DECREF(prop);
-                    return -1;
-                }
-            }
-
-            len = strlen(fieldname) + strlen(fieldfmt);
-
-            buf = PyMem_Malloc(len + 2 + 1);
-            if (buf == NULL) {
-                Py_DECREF(pair);
-                Py_DECREF(prop);
-                PyErr_NoMemory();
-                return -1;
-            }
-            sprintf(buf, "%s:%s:", fieldfmt, fieldname);
-
-            ptr = stginfo->format;
-            if (info->shape != NULL) {
-                stginfo->format = _ctypes_alloc_format_string_with_shape(
-                    info->ndim, info->shape, stginfo->format, buf);
-            } else {
-                stginfo->format = _ctypes_alloc_format_string(stginfo->format, buf);
-            }
-            PyMem_Free(ptr);
-            PyMem_Free(buf);
-
-            if (stginfo->format == NULL) {
-                Py_DECREF(pair);
-                Py_DECREF(prop);
-                return -1;
-            }
-        } else /* union */ {
-            field_size = 0;
-            size = 0;
-            bitofs = 0;
-            offset = 0;
-            align = 0;
-            prop = PyCField_FromDesc(st, desc, i,
-                                   &field_size, bitsize, &bitofs,
-                                   &size, &offset, &align,
-                                   pack, big_endian, layout_mode);
-            if (prop == NULL) {
-                Py_DECREF(pair);
-                return -1;
-            }
-            union_size = max(size, union_size);
-        }
-        total_align = max(align, total_align);
 
-        if (-1 == PyObject_SetAttr(type, name, prop)) {
-            Py_DECREF(prop);
-            Py_DECREF(pair);
-            return -1;
+        if (-1 == PyObject_SetAttr(type, prop->name, prop_obj)) {
+            goto error;
         }
-        Py_DECREF(pair);
-        Py_DECREF(prop);
-    }
-
-    if (!isStruct) {
-        size = union_size;
-    }
-
-    /* Adjust the size according to the alignment requirements */
-    aligned_size = ((size + total_align - 1) / total_align) * total_align;
-
-    if (isStruct) {
-        char *ptr;
-        Py_ssize_t padding;
-
-        /* Pad up to the full size of the struct */
-        padding = aligned_size - size;
-        if (padding > 0) {
-            ptr = stginfo->format;
-            stginfo->format = _ctypes_alloc_format_padding(ptr, padding);
-            PyMem_Free(ptr);
-            if (stginfo->format == NULL) {
-                return -1;
-            }
-        }
-
-        ptr = stginfo->format;
-        stginfo->format = _ctypes_alloc_format_string(stginfo->format, "}");
-        PyMem_Free(ptr);
-        if (stginfo->format == NULL)
-            return -1;
     }
 
     stginfo->ffi_type_pointer.alignment = Py_SAFE_DOWNCAST(total_align,
                                                            Py_ssize_t,
                                                            unsigned short);
-    stginfo->ffi_type_pointer.size = aligned_size;
+    stginfo->ffi_type_pointer.size = total_size;
 
-    stginfo->size = aligned_size;
+    stginfo->size = total_size;
     stginfo->align = total_align;
     stginfo->length = ffi_ofs + len;
 
@@ -650,7 +441,7 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
 #  define MAX_STRUCT_SIZE 16
 #endif
 
-    if (arrays_seen && (size <= MAX_STRUCT_SIZE)) {
+    if (arrays_seen && (total_size <= MAX_STRUCT_SIZE)) {
         /*
          * See bpo-22273 and gh-110190. Arrays are normally treated as
          * pointers, which is fine when an array name is being passed as
@@ -725,35 +516,19 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
         Py_ssize_t struct_index = 0; /* index into dummy structs */
 
         /* first pass to see how much memory to allocate */
-        for (i = 0; i < len; ++i) {
-            PyObject *name, *desc;
-            PyObject *pair = PySequence_GetItem(fields, i);
-            int bitsize = 0;
-
-            if (pair == NULL) {
-                return -1;
-            }
-            if (!PyArg_ParseTuple(pair, "UO|i", &name, &desc, &bitsize)) {
-                PyErr_SetString(PyExc_TypeError,
-                    "'_fields_' must be a sequence of (name, C type) pairs");
-                Py_DECREF(pair);
-                return -1;
-            }
+        for (Py_ssize_t i = 0; i < len; ++i) {
+            PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed
+            assert(prop_obj);
+            assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type));
+            CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed
 
             StgInfo *info;
-            if (PyStgInfo_FromType(st, desc, &info) < 0) {
-                Py_DECREF(pair);
-                return -1;
-            }
-            if (info == NULL) {
-                Py_DECREF(pair);
-                PyErr_Format(PyExc_TypeError,
-                    "second item in _fields_ tuple (index %zd) must be a C type",
-                    i);
-                return -1;
+            if (PyStgInfo_FromType(st, prop->proto, &info) < 0) {
+                goto error;
             }
+            assert(info);
 
-            if (!PyCArrayTypeObject_Check(st, desc)) {
+            if (!PyCArrayTypeObject_Check(st, prop->proto)) {
                 /* Not an array. Just need an ffi_type pointer. */
                 num_ffi_type_pointers++;
             }
@@ -763,15 +538,13 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
 
                 StgInfo *einfo;
                 if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) {
-                    Py_DECREF(pair);
-                    return -1;
+                    goto error;
                 }
                 if (einfo == NULL) {
-                    Py_DECREF(pair);
                     PyErr_Format(PyExc_TypeError,
                         "second item in _fields_ tuple (index %zd) must be a C type",
                         i);
-                    return -1;
+                    goto error;
                 }
                 /*
                  * We need one extra ffi_type to hold the struct, and one
@@ -781,7 +554,6 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
                 num_ffi_types++;
                 num_ffi_type_pointers += length + 1;
             }
-            Py_DECREF(pair);
         }
 
         /*
@@ -798,7 +570,7 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
 
         if (type_block == NULL) {
             PyErr_NoMemory();
-            return -1;
+            goto error;
         }
         /*
          * the first block takes up ffi_ofs + len + 1 which is the pointers *
@@ -822,48 +594,21 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
         element_index = ffi_ofs;
 
         /* second pass to actually set the type pointers */
-        for (i = 0; i < len; ++i) {
-            PyObject *name, *desc;
-            PyObject *pair = PySequence_GetItem(fields, i);
-            int bitsize = 0;
-
-            if (pair == NULL) {
-                PyMem_Free(type_block);
-                return -1;
-            }
-            /* In theory, we made this call in the first pass, so it *shouldn't*
-             * fail. However, you never know, and the code above might change
-             * later - keeping the check in here is a tad defensive but it
-             * will affect program size only slightly and performance hardly at
-             * all.
-             */
-            if (!PyArg_ParseTuple(pair, "UO|i", &name, &desc, &bitsize)) {
-                PyErr_SetString(PyExc_TypeError,
-                                "'_fields_' must be a sequence of (name, C type) pairs");
-                Py_DECREF(pair);
-                PyMem_Free(type_block);
-                return -1;
-            }
+        for (Py_ssize_t i = 0; i < len; ++i) {
+            PyObject *prop_obj = PyTuple_GET_ITEM(layout_fields, i); // borrowed
+            assert(prop_obj);
+            assert(PyType_IsSubtype(Py_TYPE(prop_obj), st->PyCField_Type));
+            CFieldObject *prop = (CFieldObject *)prop_obj; // borrowed
 
             StgInfo *info;
-            if (PyStgInfo_FromType(st, desc, &info) < 0) {
-                Py_DECREF(pair);
-                PyMem_Free(type_block);
-                return -1;
-            }
-
-            /* Possibly this check could be avoided, but see above comment. */
-            if (info == NULL) {
-                Py_DECREF(pair);
+            if (PyStgInfo_FromType(st, prop->proto, &info) < 0) {
                 PyMem_Free(type_block);
-                PyErr_Format(PyExc_TypeError,
-                             "second item in _fields_ tuple (index %zd) must be a C type",
-                             i);
-                return -1;
+                goto error;
             }
+            assert(info);
 
             assert(element_index < (ffi_ofs + len)); /* will be used below */
-            if (!PyCArrayTypeObject_Check(st, desc)) {
+            if (!PyCArrayTypeObject_Check(st, prop->proto)) {
                 /* Not an array. Just copy over the element ffi_type. */
                 element_types[element_index++] = &info->ffi_type_pointer;
             }
@@ -871,17 +616,15 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
                 Py_ssize_t length = info->length;
                 StgInfo *einfo;
                 if (PyStgInfo_FromType(st, info->proto, &einfo) < 0) {
-                    Py_DECREF(pair);
                     PyMem_Free(type_block);
-                    return -1;
+                    goto error;
                 }
                 if (einfo == NULL) {
-                    Py_DECREF(pair);
                     PyMem_Free(type_block);
                     PyErr_Format(PyExc_TypeError,
                                  "second item in _fields_ tuple (index %zd) must be a C type",
                                  i);
-                    return -1;
+                    goto error;
                 }
                 element_types[element_index++] = &structs[struct_index];
                 structs[struct_index].size = length * einfo->ffi_type_pointer.size;
@@ -898,7 +641,6 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
                 assert(dummy_index < (num_ffi_type_pointers));
                 dummy_types[dummy_index++] = NULL;
             }
-            Py_DECREF(pair);
         }
 
         element_types[element_index] = NULL;
@@ -916,9 +658,14 @@ PyCStructUnionType_update_stginfo(PyObject *type, PyObject *fields, int isStruct
     if (stginfo->flags & DICTFLAG_FINAL) {
         PyErr_SetString(PyExc_AttributeError,
                         "Structure or union cannot contain itself");
-        return -1;
+        goto error;
     }
     stginfo->flags |= DICTFLAG_FINAL;
 
-    return MakeAnonFields(type);
+    retval = MakeAnonFields(type);
+error:
+    Py_XDECREF(layout_fields);
+    Py_XDECREF(layout);
+    Py_XDECREF(format_spec_obj);
+    return retval;
 }