https://github.com/python/cpython/commit/0e53038ea825765136c5275e09f9ea9be5982b82
commit: 0e53038ea825765136c5275e09f9ea9be5982b82
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2025-03-24T14:18:34+01:00
summary:
gh-128715: Expose ctypes.CField, with info attributes (GH-128950)
- Restore max field size to sys.maxsize, as in Python 3.13 & below
- PyCField: Split out bit/byte sizes/offsets.
- Expose CField's size/offset data to Python code
- Add generic checks for all the test structs/unions, using the newly exposed
attrs
files:
A Misc/NEWS.d/next/Library/2025-01-17-17-35-16.gh-issue-128715.tQjo89.rst
M Doc/library/ctypes.rst
M Doc/whatsnew/3.14.rst
M Include/internal/pycore_global_objects_fini_generated.h
M Include/internal/pycore_global_strings.h
M Include/internal/pycore_runtime_init_generated.h
M Include/internal/pycore_unicodeobject_generated.h
M Lib/ctypes/__init__.py
M Lib/ctypes/_layout.py
M Lib/test/test_ctypes/_support.py
M Lib/test/test_ctypes/test_aligned_structures.py
M Lib/test/test_ctypes/test_anon.py
M Lib/test/test_ctypes/test_bitfields.py
M Lib/test/test_ctypes/test_bytes.py
M Lib/test/test_ctypes/test_byteswap.py
M Lib/test/test_ctypes/test_funcptr.py
M Lib/test/test_ctypes/test_generated_structs.py
M Lib/test/test_ctypes/test_struct_fields.py
M Lib/test/test_ctypes/test_structunion.py
M Lib/test/test_ctypes/test_structures.py
M Modules/_ctypes/_ctypes_test_generated.c.h
M Modules/_ctypes/callbacks.c
M Modules/_ctypes/callproc.c
M Modules/_ctypes/cfield.c
M Modules/_ctypes/clinic/cfield.c.h
M Modules/_ctypes/ctypes.h
M Modules/_ctypes/stgdict.c
diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index bf89700f54b7e9..1a7b456a8fc6ab 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -657,12 +657,13 @@ Nested structures can also be initialized in the
constructor in several ways::
>>> r = RECT((1, 2), (3, 4))
Field :term:`descriptor`\s can be retrieved from the *class*, they are useful
-for debugging because they can provide useful information::
+for debugging because they can provide useful information.
+See :class:`CField`::
- >>> print(POINT.x)
- <Field type=c_long, ofs=0, size=4>
- >>> print(POINT.y)
- <Field type=c_long, ofs=4, size=4>
+ >>> POINT.x
+ <ctypes.CField 'x' type=c_int, ofs=0, size=4>
+ >>> POINT.y
+ <ctypes.CField 'y' type=c_int, ofs=4, size=4>
>>>
@@ -2812,6 +2813,98 @@ fields, or any other data types containing pointer type
fields.
present in :attr:`_fields_`.
+.. class:: CField(*args, **kw)
+
+ Descriptor for fields of a :class:`Structure` and :class:`Union`.
+ For example::
+
+ >>> class Color(Structure):
+ ... _fields_ = (
+ ... ('red', c_uint8),
+ ... ('green', c_uint8),
+ ... ('blue', c_uint8),
+ ... ('intense', c_bool, 1),
+ ... ('blinking', c_bool, 1),
+ ... )
+ ...
+ >>> Color.red
+ <ctypes.CField 'red' type=c_ubyte, ofs=0, size=1>
+ >>> Color.green.type
+ <class 'ctypes.c_ubyte'>
+ >>> Color.blue.byte_offset
+ 2
+ >>> Color.intense
+ <ctypes.CField 'intense' type=c_bool, ofs=3, bit_size=1, bit_offset=0>
+ >>> Color.blinking.bit_offset
+ 1
+
+ All attributes are read-only.
+
+ :class:`!CField` objects are created via :attr:`~Structure._fields_`;
+ do not instantiate the class directly.
+
+ .. versionadded:: next
+
+ Previously, descriptors only had ``offset`` and ``size`` attributes
+ and a readable string representation; the :class:`!CField` class was not
+ available directly.
+
+ .. attribute:: name
+
+ Name of the field, as a string.
+
+ .. attribute:: type
+
+ Type of the field, as a :ref:`ctypes class <ctypes-data-types>`.
+
+ .. attribute:: offset
+ byte_offset
+
+ Offset of the field, in bytes.
+
+ For bitfields, this is the offset of the underlying byte-aligned
+ *storage unit*; see :attr:`~CField.bit_offset`.
+
+ .. attribute:: byte_size
+
+ Size of the field, in bytes.
+
+ For bitfields, this is the size of the underlying *storage unit*.
+ Typically, it has the same size as the bitfield's type.
+
+ .. attribute:: size
+
+ For non-bitfields, equivalent to :attr:`~CField.byte_size`.
+
+ For bitfields, this contains a backwards-compatible bit-packed
+ value that combines :attr:`~CField.bit_size` and
+ :attr:`~CField.bit_offset`.
+ Prefer using the explicit attributes instead.
+
+ .. attribute:: is_bitfield
+
+ True if this is a bitfield.
+
+ .. attribute:: bit_offset
+ bit_size
+
+ The location of a bitfield within its *storage unit*, that is, within
+ :attr:`~CField.byte_size` bytes of memory starting at
+ :attr:`~CField.byte_offset`.
+
+ To get the field's value, read the storage unit as an integer,
+ :ref:`shift left <shifting>` by :attr:`!bit_offset` and
+ take the :attr:`!bit_size` least significant bits.
+
+ For non-bitfields, :attr:`!bit_offset` is zero
+ and :attr:`!bit_size` is equal to ``byte_size * 8``.
+
+ .. attribute:: is_anonymous
+
+ True if this field is anonymous, that is, it contains nested sub-fields
+ that should be be merged into a containing structure or union.
+
+
.. _ctypes-arrays-pointers:
Arrays and pointers
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 767cf9a1f08dc2..a7a4617cb1d692 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -502,6 +502,11 @@ ctypes
to help match a non-default ABI.
(Contributed by Petr Viktorin in :gh:`97702`.)
+* The class of :class:`~ctypes.Structure`/:class:`~ctypes.Union`
+ field descriptors is now available as :class:`~ctypes.CField`,
+ and has new attributes to aid debugging and introspection.
+ (Contributed by Petr Viktorin in :gh:`128715`.)
+
* On Windows, the :exc:`~ctypes.COMError` exception is now public.
(Contributed by Jun Komoda in :gh:`126686`.)
diff --git a/Include/internal/pycore_global_objects_fini_generated.h
b/Include/internal/pycore_global_objects_fini_generated.h
index 90214a314031d1..0612184e10c70c 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -756,6 +756,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_get_sourcefile));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_handle_fromlist));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_initializing));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_internal_use));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_io));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_is_text_encoding));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_isatty_open_only));
@@ -806,6 +807,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_offset));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bit_size));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(block));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bound));
@@ -816,6 +818,8 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(buffers));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bufsize));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(builtins));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(byte_offset));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(byte_size));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(byteorder));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bytes));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(bytes_per_sep));
diff --git a/Include/internal/pycore_global_strings.h
b/Include/internal/pycore_global_strings.h
index 5056128dc97ca0..bc21fa5e4ca761 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -247,6 +247,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_get_sourcefile)
STRUCT_FOR_ID(_handle_fromlist)
STRUCT_FOR_ID(_initializing)
+ STRUCT_FOR_ID(_internal_use)
STRUCT_FOR_ID(_io)
STRUCT_FOR_ID(_is_text_encoding)
STRUCT_FOR_ID(_isatty_open_only)
@@ -297,6 +298,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(before)
STRUCT_FOR_ID(big)
STRUCT_FOR_ID(binary_form)
+ STRUCT_FOR_ID(bit_offset)
STRUCT_FOR_ID(bit_size)
STRUCT_FOR_ID(block)
STRUCT_FOR_ID(bound)
@@ -307,6 +309,8 @@ struct _Py_global_strings {
STRUCT_FOR_ID(buffers)
STRUCT_FOR_ID(bufsize)
STRUCT_FOR_ID(builtins)
+ STRUCT_FOR_ID(byte_offset)
+ STRUCT_FOR_ID(byte_size)
STRUCT_FOR_ID(byteorder)
STRUCT_FOR_ID(bytes)
STRUCT_FOR_ID(bytes_per_sep)
diff --git a/Include/internal/pycore_runtime_init_generated.h
b/Include/internal/pycore_runtime_init_generated.h
index 4f928cc050bf8e..4a7111a01bf00c 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -754,6 +754,7 @@ extern "C" {
INIT_ID(_get_sourcefile), \
INIT_ID(_handle_fromlist), \
INIT_ID(_initializing), \
+ INIT_ID(_internal_use), \
INIT_ID(_io), \
INIT_ID(_is_text_encoding), \
INIT_ID(_isatty_open_only), \
@@ -804,6 +805,7 @@ extern "C" {
INIT_ID(before), \
INIT_ID(big), \
INIT_ID(binary_form), \
+ INIT_ID(bit_offset), \
INIT_ID(bit_size), \
INIT_ID(block), \
INIT_ID(bound), \
@@ -814,6 +816,8 @@ extern "C" {
INIT_ID(buffers), \
INIT_ID(bufsize), \
INIT_ID(builtins), \
+ INIT_ID(byte_offset), \
+ INIT_ID(byte_size), \
INIT_ID(byteorder), \
INIT_ID(bytes), \
INIT_ID(bytes_per_sep), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h
b/Include/internal/pycore_unicodeobject_generated.h
index 5b78d038fc1192..1ec99a1b5b3a5c 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -776,6 +776,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(_internal_use);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(_io);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
@@ -976,6 +980,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(bit_offset);
+ _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));
@@ -1016,6 +1024,14 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp)
{
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(byte_offset);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(byte_size);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(byteorder);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index 8e2a2926f7a853..d9e55816211737 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -12,6 +12,7 @@
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
from _ctypes import ArgumentError
from _ctypes import SIZEOF_TIME_T
+from _ctypes import CField
from struct import calcsize as _calcsize
diff --git a/Lib/ctypes/_layout.py b/Lib/ctypes/_layout.py
index adda3f9a6f2acc..beb3b86414c010 100644
--- a/Lib/ctypes/_layout.py
+++ b/Lib/ctypes/_layout.py
@@ -19,29 +19,6 @@ def round_up(n, multiple):
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
@@ -213,13 +190,10 @@ def get_layout(cls, input_fields, is_struct, base):
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
+ bit_offset = next_bit_offset - 8 * offset
+ assert 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
@@ -253,18 +227,17 @@ def get_layout(cls, input_fields, is_struct, base):
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
+ bit_offset = last_field_bit_size + next_bit_offset
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)
+ if is_bitfield and big_endian:
+ # On big-endian architectures, bit fields are also laid out
+ # starting with the big end.
+ bit_offset = type_bit_size - bit_size - bit_offset
# Add the format spec parts
if is_struct:
@@ -286,16 +259,21 @@ def get_layout(cls, input_fields, is_struct, base):
# 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")
+ f"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,
+ byte_size=type_size,
+ byte_offset=offset,
bit_size=bit_size if is_bitfield else None,
+ bit_offset=bit_offset if is_bitfield else None,
index=i,
+
+ # Do not use CField outside ctypes, yet.
+ # The constructor is internal API and may change without warning.
+ _internal_use=True,
))
if is_bitfield and not gcc_layout:
assert type_bit_size > 0
diff --git a/Lib/test/test_ctypes/_support.py b/Lib/test/test_ctypes/_support.py
index e4c2b33825ae8f..946d654a19aff8 100644
--- a/Lib/test/test_ctypes/_support.py
+++ b/Lib/test/test_ctypes/_support.py
@@ -2,15 +2,13 @@
import ctypes
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
+import sys
+from test import support
_CData = Structure.__base__
assert _CData.__name__ == "_CData"
-class _X(Structure):
- _fields_ = [("x", ctypes.c_int)]
-CField = type(_X.x)
-
# metaclasses
PyCStructType = type(Structure)
UnionType = type(Union)
@@ -22,3 +20,132 @@ class _X(Structure):
# type flags
Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7
Py_TPFLAGS_IMMUTABLETYPE = 1 << 8
+
+
+def is_underaligned(ctype):
+ """Return true when type's alignment is less than its size.
+
+ A famous example is 64-bit int on 32-bit x86.
+ """
+ return ctypes.alignment(ctype) < ctypes.sizeof(ctype)
+
+
+class StructCheckMixin:
+ def check_struct(self, structure):
+ """Assert that a structure is well-formed"""
+ self._check_struct_or_union(structure, is_struct=True)
+
+ def check_union(self, union):
+ """Assert that a union is well-formed"""
+ self._check_struct_or_union(union, is_struct=False)
+
+ def check_struct_or_union(self, cls):
+ if issubclass(cls, Structure):
+ self._check_struct_or_union(cls, is_struct=True)
+ elif issubclass(cls, Union):
+ self._check_struct_or_union(cls, is_struct=False)
+ else:
+ raise TypeError(cls)
+
+ def _check_struct_or_union(self, cls, is_struct):
+
+ # Check that fields are not overlapping (for structs),
+ # and that their metadata is consistent.
+
+ used_bits = 0
+
+ is_little_endian = (
+ hasattr(cls, '_swappedbytes_') ^ (sys.byteorder == 'little'))
+
+ anon_names = getattr(cls, '_anonymous_', ())
+ cls_size = ctypes.sizeof(cls)
+ for name, requested_type, *rest_of_tuple in cls._fields_:
+ field = getattr(cls, name)
+ with self.subTest(name=name, field=field):
+ is_bitfield = len(rest_of_tuple) > 0
+
+ # name
+ self.assertEqual(field.name, name)
+
+ # type
+ self.assertEqual(field.type, requested_type)
+
+ # offset === byte_offset
+ self.assertEqual(field.byte_offset, field.offset)
+ if not is_struct:
+ self.assertEqual(field.byte_offset, 0)
+
+ # byte_size
+ self.assertEqual(field.byte_size, ctypes.sizeof(field.type))
+ self.assertGreaterEqual(field.byte_size, 0)
+
+ # Check that the field is inside the struct.
+ # See gh-130410 for why this is skipped for bitfields of
+ # underaligned types. Later in this function (see `bit_end`)
+ # we assert that the value *bits* are inside the struct.
+ if not (field.is_bitfield and is_underaligned(field.type)):
+ self.assertLessEqual(field.byte_offset + field.byte_size,
+ cls_size)
+
+ # size
+ self.assertGreaterEqual(field.size, 0)
+ if is_bitfield:
+ # size has backwards-compatible bit-packed info
+ expected_size = (field.bit_size << 16) + field.bit_offset
+ self.assertEqual(field.size, expected_size)
+ else:
+ # size == byte_size
+ self.assertEqual(field.size, field.byte_size)
+
+ # is_bitfield (bool)
+ self.assertIs(field.is_bitfield, is_bitfield)
+
+ # bit_offset
+ if is_bitfield:
+ self.assertGreaterEqual(field.bit_offset, 0)
+ self.assertLessEqual(field.bit_offset + field.bit_size,
+ field.byte_size * 8)
+ else:
+ self.assertEqual(field.bit_offset, 0)
+ if not is_struct:
+ if is_little_endian:
+ self.assertEqual(field.bit_offset, 0)
+ else:
+ self.assertEqual(field.bit_offset,
+ field.byte_size * 8 - field.bit_size)
+
+ # bit_size
+ if is_bitfield:
+ self.assertGreaterEqual(field.bit_size, 0)
+ self.assertLessEqual(field.bit_size, field.byte_size * 8)
+ [requested_bit_size] = rest_of_tuple
+ self.assertEqual(field.bit_size, requested_bit_size)
+ else:
+ self.assertEqual(field.bit_size, field.byte_size * 8)
+
+ # is_anonymous (bool)
+ self.assertIs(field.is_anonymous, name in anon_names)
+
+ # In a struct, field should not overlap.
+ # (Test skipped if the structs is enormous.)
+ if is_struct and cls_size < 10_000:
+ # Get a mask indicating where the field is within the
struct
+ if is_little_endian:
+ tp_shift = field.byte_offset * 8
+ else:
+ tp_shift = (cls_size
+ - field.byte_offset
+ - field.byte_size) * 8
+ mask = (1 << field.bit_size) - 1
+ mask <<= (tp_shift + field.bit_offset)
+ assert mask.bit_count() == field.bit_size
+ # Check that these bits aren't shared with previous fields
+ self.assertEqual(used_bits & mask, 0)
+ # Mark the bits for future checks
+ used_bits |= mask
+
+ # field is inside cls
+ bit_end = (field.byte_offset * 8
+ + field.bit_offset
+ + field.bit_size)
+ self.assertLessEqual(bit_end, cls_size * 8)
diff --git a/Lib/test/test_ctypes/test_aligned_structures.py
b/Lib/test/test_ctypes/test_aligned_structures.py
index a208fb9a00966a..26d24f31b29f7b 100644
--- a/Lib/test/test_ctypes/test_aligned_structures.py
+++ b/Lib/test/test_ctypes/test_aligned_structures.py
@@ -5,9 +5,9 @@
)
import struct
import unittest
+from ._support import StructCheckMixin
-
-class TestAlignedStructures(unittest.TestCase):
+class TestAlignedStructures(unittest.TestCase, StructCheckMixin):
def test_aligned_string(self):
for base, e in (
(LittleEndianStructure, "<"),
@@ -19,12 +19,14 @@ class Aligned(base):
_fields_ = [
('value', c_char * 12)
]
+ self.check_struct(Aligned)
class Main(base):
_fields_ = [
('first', c_uint32),
('string', Aligned),
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(main.first, 7)
@@ -46,12 +48,14 @@ class SomeBools(base):
("bool1", c_ubyte),
("bool2", c_ubyte),
]
+ self.check_struct(SomeBools)
class Main(base):
_fields_ = [
("x", c_ubyte),
("y", SomeBools),
("z", c_ubyte),
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(alignment(SomeBools), 4)
@@ -75,11 +79,13 @@ class SomeBoolsTooBig(base):
("bool2", c_ubyte),
("bool3", c_ubyte),
]
+ self.check_struct(SomeBoolsTooBig)
class Main(base):
_fields_ = [
("y", SomeBoolsTooBig),
("z", c_uint32),
]
+ self.check_struct(Main)
with self.assertRaises(ValueError) as ctx:
Main.from_buffer(data)
self.assertEqual(
@@ -98,18 +104,21 @@ class UnalignedSub(base):
_fields_ = [
("x", c_uint32),
]
+ self.check_struct(UnalignedSub)
class AlignedStruct(UnalignedSub):
_align_ = 8
_fields_ = [
("y", c_uint32),
]
+ self.check_struct(AlignedStruct)
class Main(base):
_fields_ = [
("a", c_uint32),
("b", AlignedStruct)
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(alignment(main.b), 8)
@@ -134,12 +143,14 @@ class AlignedUnion(ubase):
("a", c_uint32),
("b", c_ubyte * 7),
]
+ self.check_union(AlignedUnion)
class Main(sbase):
_fields_ = [
("first", c_uint32),
("union", AlignedUnion),
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(main.first, 1)
@@ -162,18 +173,21 @@ class Sub(sbase):
("x", c_uint32),
("y", c_uint32),
]
+ self.check_struct(Sub)
class MainUnion(ubase):
_fields_ = [
("a", c_uint32),
("b", Sub),
]
+ self.check_union(MainUnion)
class Main(sbase):
_fields_ = [
("first", c_uint32),
("union", MainUnion),
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(Main.first.size, 4)
@@ -198,17 +212,20 @@ class SubUnion(ubase):
("unsigned", c_ubyte),
("signed", c_byte),
]
+ self.check_union(SubUnion)
class MainUnion(SubUnion):
_fields_ = [
("num", c_uint32)
]
+ self.check_union(SubUnion)
class Main(sbase):
_fields_ = [
("first", c_uint16),
("union", MainUnion),
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(main.union.num, 0xD60102D7)
@@ -232,11 +249,13 @@ class SubUnion(ubase):
("unsigned", c_ubyte),
("signed", c_byte),
]
+ self.check_union(SubUnion)
class Main(SubUnion):
_fields_ = [
("num", c_uint32)
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(alignment(main), 8)
@@ -258,6 +277,7 @@ class Inner(sbase):
("x", c_uint16),
("y", c_uint16),
]
+ self.check_struct(Inner)
class Main(sbase):
_pack_ = 1
@@ -266,6 +286,7 @@ class Main(sbase):
("b", Inner),
("c", c_ubyte),
]
+ self.check_struct(Main)
main = Main.from_buffer(data)
self.assertEqual(sizeof(main), 10)
diff --git a/Lib/test/test_ctypes/test_anon.py
b/Lib/test/test_ctypes/test_anon.py
index b36397b510fefe..2e16e708635989 100644
--- a/Lib/test/test_ctypes/test_anon.py
+++ b/Lib/test/test_ctypes/test_anon.py
@@ -1,20 +1,23 @@
import unittest
import test.support
from ctypes import c_int, Union, Structure, sizeof
+from ._support import StructCheckMixin
-class AnonTest(unittest.TestCase):
+class AnonTest(unittest.TestCase, StructCheckMixin):
def test_anon(self):
class ANON(Union):
_fields_ = [("a", c_int),
("b", c_int)]
+ self.check_union(ANON)
class Y(Structure):
_fields_ = [("x", c_int),
("_", ANON),
("y", c_int)]
_anonymous_ = ["_"]
+ self.check_struct(Y)
self.assertEqual(Y.a.offset, sizeof(c_int))
self.assertEqual(Y.b.offset, sizeof(c_int))
@@ -52,17 +55,20 @@ class Name(Structure):
def test_nested(self):
class ANON_S(Structure):
_fields_ = [("a", c_int)]
+ self.check_struct(ANON_S)
class ANON_U(Union):
_fields_ = [("_", ANON_S),
("b", c_int)]
_anonymous_ = ["_"]
+ self.check_union(ANON_U)
class Y(Structure):
_fields_ = [("x", c_int),
("_", ANON_U),
("y", c_int)]
_anonymous_ = ["_"]
+ self.check_struct(Y)
self.assertEqual(Y.x.offset, 0)
self.assertEqual(Y.a.offset, sizeof(c_int))
diff --git a/Lib/test/test_ctypes/test_bitfields.py
b/Lib/test/test_ctypes/test_bitfields.py
index 19ba2f4484e7da..dc81e752567c42 100644
--- a/Lib/test/test_ctypes/test_bitfields.py
+++ b/Lib/test/test_ctypes/test_bitfields.py
@@ -10,27 +10,33 @@
Union)
from test import support
from test.support import import_helper
+from ._support import StructCheckMixin
_ctypes_test = import_helper.import_module("_ctypes_test")
+TEST_FIELDS = (
+ ("A", c_int, 1),
+ ("B", c_int, 2),
+ ("C", c_int, 3),
+ ("D", c_int, 4),
+ ("E", c_int, 5),
+ ("F", c_int, 6),
+ ("G", c_int, 7),
+ ("H", c_int, 8),
+ ("I", c_int, 9),
+
+ ("M", c_short, 1),
+ ("N", c_short, 2),
+ ("O", c_short, 3),
+ ("P", c_short, 4),
+ ("Q", c_short, 5),
+ ("R", c_short, 6),
+ ("S", c_short, 7),
+)
+
+
class BITS(Structure):
- _fields_ = [("A", c_int, 1),
- ("B", c_int, 2),
- ("C", c_int, 3),
- ("D", c_int, 4),
- ("E", c_int, 5),
- ("F", c_int, 6),
- ("G", c_int, 7),
- ("H", c_int, 8),
- ("I", c_int, 9),
-
- ("M", c_short, 1),
- ("N", c_short, 2),
- ("O", c_short, 3),
- ("P", c_short, 4),
- ("Q", c_short, 5),
- ("R", c_short, 6),
- ("S", c_short, 7)]
+ _fields_ = TEST_FIELDS
func = CDLL(_ctypes_test.__file__).unpack_bitfields
func.argtypes = POINTER(BITS), c_char
@@ -38,23 +44,12 @@ class BITS(Structure):
class BITS_msvc(Structure):
_layout_ = "ms"
- _fields_ = [("A", c_int, 1),
- ("B", c_int, 2),
- ("C", c_int, 3),
- ("D", c_int, 4),
- ("E", c_int, 5),
- ("F", c_int, 6),
- ("G", c_int, 7),
- ("H", c_int, 8),
- ("I", c_int, 9),
-
- ("M", c_short, 1),
- ("N", c_short, 2),
- ("O", c_short, 3),
- ("P", c_short, 4),
- ("Q", c_short, 5),
- ("R", c_short, 6),
- ("S", c_short, 7)]
+ _fields_ = TEST_FIELDS
+
+
+class BITS_gcc(Structure):
+ _layout_ = "gcc-sysv"
+ _fields_ = TEST_FIELDS
try:
@@ -124,13 +119,19 @@ def test_shorts_msvc_mode(self):
unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong)
int_types = unsigned_int_types + signed_int_types
-class BitFieldTest(unittest.TestCase):
+class BitFieldTest(unittest.TestCase, StructCheckMixin):
+
+ def test_generic_checks(self):
+ self.check_struct(BITS)
+ self.check_struct(BITS_msvc)
+ self.check_struct(BITS_gcc)
def test_longlong(self):
class X(Structure):
_fields_ = [("a", c_longlong, 1),
("b", c_longlong, 62),
("c", c_longlong, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_longlong))
x = X()
@@ -142,6 +143,7 @@ class X(Structure):
_fields_ = [("a", c_ulonglong, 1),
("b", c_ulonglong, 62),
("c", c_ulonglong, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_longlong))
x = X()
@@ -159,6 +161,7 @@ class X(Structure):
("a", c_typ, 3),
("b", c_typ, 3),
("c", c_typ, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ)*2)
x = X()
@@ -178,6 +181,7 @@ class X(Structure):
_fields_ = [("a", c_typ, 3),
("b", c_typ, 3),
("c", c_typ, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ))
x = X()
@@ -210,12 +214,14 @@ def test_nonint_types(self):
class Empty(Structure):
_fields_ = []
+ self.check_struct(Empty)
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)]
+ self.check_struct(Dummy)
result = self.fail_fields(("a", Dummy, 1))
self.assertEqual(result, (TypeError, 'bit fields not allowed for type
Dummy'))
@@ -240,10 +246,12 @@ def test_single_bitfield_size(self):
class X(Structure):
_fields_ = [("a", c_typ, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ))
class X(Structure):
_fields_ = [("a", c_typ, sizeof(c_typ)*8)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_typ))
result = self.fail_fields(("a", c_typ, sizeof(c_typ)*8 + 1))
@@ -255,6 +263,7 @@ class X(Structure):
_fields_ = [("a", c_short, 1),
("b", c_short, 14),
("c", c_short, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_short))
class X(Structure):
@@ -262,6 +271,7 @@ class X(Structure):
("a1", c_short),
("b", c_short, 14),
("c", c_short, 1)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_short)*3)
self.assertEqual(X.a.offset, 0)
self.assertEqual(X.a1.offset, sizeof(c_short))
@@ -272,6 +282,7 @@ class X(Structure):
_fields_ = [("a", c_short, 3),
("b", c_short, 14),
("c", c_short, 14)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_short)*3)
self.assertEqual(X.a.offset, sizeof(c_short)*0)
self.assertEqual(X.b.offset, sizeof(c_short)*1)
@@ -287,6 +298,7 @@ def test_mixed_1(self):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_int, 4)]
+ self.check_struct(X)
if os.name == "nt":
self.assertEqual(sizeof(X), sizeof(c_int)*2)
else:
@@ -296,12 +308,14 @@ def test_mixed_2(self):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_int, 32)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), alignment(c_int)+sizeof(c_int))
def test_mixed_3(self):
class X(Structure):
_fields_ = [("a", c_byte, 4),
("b", c_ubyte, 4)]
+ self.check_struct(X)
self.assertEqual(sizeof(X), sizeof(c_byte))
def test_mixed_4(self):
@@ -312,6 +326,7 @@ class X(Structure):
("d", c_short, 4),
("e", c_short, 4),
("f", c_int, 24)]
+ self.check_struct(X)
# MSVC does NOT combine c_short and c_int into one field, GCC
# does (unless GCC is run with '-mms-bitfields' which
# produces code compatible with MSVC).
@@ -325,6 +340,7 @@ class X(Structure):
_fields_ = [
('A', c_uint, 1),
('B', c_ushort, 16)]
+ self.check_struct(X)
a = X()
a.A = 0
a.B = 1
@@ -335,6 +351,7 @@ class X(Structure):
_fields_ = [
('A', c_ulonglong, 1),
('B', c_uint, 32)]
+ self.check_struct(X)
a = X()
a.A = 0
a.B = 1
@@ -348,6 +365,7 @@ class X(Structure):
("A", c_uint32),
('B', c_uint32, 20),
('C', c_uint64, 24)]
+ self.check_struct(X)
self.assertEqual(16, sizeof(X))
def test_mixed_8(self):
@@ -357,6 +375,7 @@ class Foo(Structure):
("B", c_uint32, 32),
("C", c_ulonglong, 1),
]
+ self.check_struct(Foo)
class Bar(Structure):
_fields_ = [
@@ -364,6 +383,7 @@ class Bar(Structure):
("B", c_uint32),
("C", c_ulonglong, 1),
]
+ self.check_struct(Bar)
self.assertEqual(sizeof(Foo), sizeof(Bar))
def test_mixed_9(self):
@@ -372,6 +392,7 @@ class X(Structure):
("A", c_uint8),
("B", c_uint32, 1),
]
+ self.check_struct(X)
if sys.platform == 'win32':
self.assertEqual(8, sizeof(X))
else:
@@ -385,6 +406,7 @@ class X(Structure):
("A", c_uint32, 1),
("B", c_uint64, 1),
]
+ self.check_struct(X)
if sys.platform == 'win32':
self.assertEqual(8, alignment(X))
self.assertEqual(16, sizeof(X))
@@ -399,6 +421,7 @@ class TestStruct(Structure):
("Field1", c_uint32, field_width),
("Field2", c_uint8, 8)
]
+ self.check_struct(TestStruct)
cmd = TestStruct()
cmd.Field2 = 1
@@ -442,6 +465,9 @@ class Good(Structure):
("b0", c_uint16, 4),
("b1", c_uint16, 12),
]
+ self.check_struct(Bad)
+ self.check_struct(GoodA)
+ self.check_struct(Good)
self.assertEqual(3, sizeof(Bad))
self.assertEqual(3, sizeof(Good))
@@ -461,6 +487,7 @@ class MyStructure(Structure):
("C", c_uint32, 20),
("R2", c_uint32, 2)
]
+ self.check_struct(MyStructure)
self.assertEqual(8, sizeof(MyStructure))
def test_gh_86098(self):
@@ -470,6 +497,7 @@ class X(Structure):
("b", c_uint8, 8),
("c", c_uint32, 16)
]
+ self.check_struct(X)
if sys.platform == 'win32':
self.assertEqual(8, sizeof(X))
else:
@@ -484,9 +512,13 @@ class Y(Structure):
_anonymous_ = ["_"]
_fields_ = [("_", X)]
+ self.check_struct(X)
+ self.check_struct(Y)
+
def test_uint32(self):
class X(Structure):
_fields_ = [("a", c_uint32, 32)]
+ self.check_struct(X)
x = X()
x.a = 10
self.assertEqual(x.a, 10)
@@ -496,6 +528,7 @@ class X(Structure):
def test_uint64(self):
class X(Structure):
_fields_ = [("a", c_uint64, 64)]
+ self.check_struct(X)
x = X()
x.a = 10
self.assertEqual(x.a, 10)
@@ -508,6 +541,7 @@ class Little(LittleEndianStructure):
_fields_ = [("a", c_uint32, 24),
("b", c_uint32, 4),
("c", c_uint32, 4)]
+ self.check_struct(Little)
b = bytearray(4)
x = Little.from_buffer(b)
x.a = 0xabcdef
@@ -521,6 +555,7 @@ class Big(BigEndianStructure):
_fields_ = [("a", c_uint32, 24),
("b", c_uint32, 4),
("c", c_uint32, 4)]
+ self.check_struct(Big)
b = bytearray(4)
x = Big.from_buffer(b)
x.a = 0xabcdef
@@ -533,6 +568,7 @@ class BitfieldUnion(Union):
_fields_ = [("a", c_uint32, 1),
("b", c_uint32, 2),
("c", c_uint32, 3)]
+ self.check_union(BitfieldUnion)
self.assertEqual(sizeof(BitfieldUnion), 4)
b = bytearray(4)
x = BitfieldUnion.from_buffer(b)
diff --git a/Lib/test/test_ctypes/test_bytes.py
b/Lib/test/test_ctypes/test_bytes.py
index fa11e1bbd49faf..0e7f81b9482e06 100644
--- a/Lib/test/test_ctypes/test_bytes.py
+++ b/Lib/test/test_ctypes/test_bytes.py
@@ -3,9 +3,10 @@
import unittest
from _ctypes import _SimpleCData
from ctypes import Structure, c_char, c_char_p, c_wchar, c_wchar_p
+from ._support import StructCheckMixin
-class BytesTest(unittest.TestCase):
+class BytesTest(unittest.TestCase, StructCheckMixin):
def test_c_char(self):
x = c_char(b"x")
self.assertRaises(TypeError, c_char, "x")
@@ -40,6 +41,7 @@ def test_c_wchar_p(self):
def test_struct(self):
class X(Structure):
_fields_ = [("a", c_char * 3)]
+ self.check_struct(X)
x = X(b"abc")
self.assertRaises(TypeError, X, "abc")
@@ -49,6 +51,7 @@ class X(Structure):
def test_struct_W(self):
class X(Structure):
_fields_ = [("a", c_wchar * 3)]
+ self.check_struct(X)
x = X("abc")
self.assertRaises(TypeError, X, b"abc")
diff --git a/Lib/test/test_ctypes/test_byteswap.py
b/Lib/test/test_ctypes/test_byteswap.py
index 78eff0392c4548..072c60d53dd8cb 100644
--- a/Lib/test/test_ctypes/test_byteswap.py
+++ b/Lib/test/test_ctypes/test_byteswap.py
@@ -11,6 +11,7 @@
c_short, c_ushort, c_int, c_uint,
c_long, c_ulong, c_longlong, c_ulonglong,
c_uint32, c_float, c_double)
+from ._support import StructCheckMixin
def bin(s):
@@ -24,15 +25,17 @@ def bin(s):
#
# For Structures and Unions, these types are created on demand.
-class Test(unittest.TestCase):
+class Test(unittest.TestCase, StructCheckMixin):
def test_slots(self):
class BigPoint(BigEndianStructure):
__slots__ = ()
_fields_ = [("x", c_int), ("y", c_int)]
+ self.check_struct(BigPoint)
class LowPoint(LittleEndianStructure):
__slots__ = ()
_fields_ = [("x", c_int), ("y", c_int)]
+ self.check_struct(LowPoint)
big = BigPoint()
little = LowPoint()
@@ -200,6 +203,7 @@ def test_struct_fields_unsupported_byte_order(self):
with self.assertRaises(TypeError):
class T(BigEndianStructure if sys.byteorder == "little" else
LittleEndianStructure):
_fields_ = fields + [("x", typ)]
+ self.check_struct(T)
def test_struct_struct(self):
@@ -219,9 +223,11 @@ def test_struct_struct(self):
class NestedStructure(nested):
_fields_ = [("x", c_uint32),
("y", c_uint32)]
+ self.check_struct(NestedStructure)
class TestStructure(parent):
_fields_ = [("point", NestedStructure)]
+ self.check_struct(TestStructure)
self.assertEqual(len(data), sizeof(TestStructure))
ptr = POINTER(TestStructure)
@@ -248,6 +254,7 @@ class S(base):
("h", c_short),
("i", c_int),
("d", c_double)]
+ self.check_struct(S)
s1 = S(0x12, 0x1234, 0x12345678, 3.14)
s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14)
@@ -271,6 +278,7 @@ class S(base):
("_2", c_byte),
("d", c_double)]
+ self.check_struct(S)
s1 = S()
s1.b = 0x12
@@ -298,6 +306,7 @@ class S(Structure):
("_2", c_byte),
("d", c_double)]
+ self.check_struct(S)
s1 = S()
s1.b = 0x12
@@ -334,6 +343,7 @@ def test_union_fields_unsupported_byte_order(self):
with self.assertRaises(TypeError):
class T(BigEndianUnion if sys.byteorder == "little" else
LittleEndianUnion):
_fields_ = fields + [("x", typ)]
+ self.check_union(T)
def test_union_struct(self):
# nested structures in unions with different byteorders
@@ -352,9 +362,11 @@ def test_union_struct(self):
class NestedStructure(nested):
_fields_ = [("x", c_uint32),
("y", c_uint32)]
+ self.check_struct(NestedStructure)
class TestUnion(parent):
_fields_ = [("point", NestedStructure)]
+ self.check_union(TestUnion)
self.assertEqual(len(data), sizeof(TestUnion))
ptr = POINTER(TestUnion)
@@ -374,12 +386,15 @@ def
test_build_struct_union_opposite_system_byteorder(self):
class S1(_Structure):
_fields_ = [("a", c_byte), ("b", c_byte)]
+ self.check_struct(S1)
class U1(_Union):
_fields_ = [("s1", S1), ("ab", c_short)]
+ self.check_union(U1)
class S2(_Structure):
_fields_ = [("u1", U1), ("c", c_byte)]
+ self.check_struct(S2)
if __name__ == "__main__":
diff --git a/Lib/test/test_ctypes/test_funcptr.py
b/Lib/test/test_ctypes/test_funcptr.py
index 8362fb16d94dcd..be641da30eadae 100644
--- a/Lib/test/test_ctypes/test_funcptr.py
+++ b/Lib/test/test_ctypes/test_funcptr.py
@@ -5,7 +5,7 @@
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
from ._support import (_CData, PyCFuncPtrType,
Py_TPFLAGS_DISALLOW_INSTANTIATION,
- Py_TPFLAGS_IMMUTABLETYPE)
+ Py_TPFLAGS_IMMUTABLETYPE, StructCheckMixin)
try:
@@ -17,7 +17,7 @@
lib = CDLL(_ctypes_test.__file__)
-class CFuncPtrTestCase(unittest.TestCase):
+class CFuncPtrTestCase(unittest.TestCase, StructCheckMixin):
def test_inheritance_hierarchy(self):
self.assertEqual(_CFuncPtr.mro(), [_CFuncPtr, _CData, object])
@@ -88,6 +88,7 @@ class WNDCLASS(Structure):
("hCursor", HCURSOR),
("lpszMenuName", LPCTSTR),
("lpszClassName", LPCTSTR)]
+ self.check_struct(WNDCLASS)
wndclass = WNDCLASS()
wndclass.lpfnWndProc = WNDPROC(wndproc)
diff --git a/Lib/test/test_ctypes/test_generated_structs.py
b/Lib/test/test_ctypes/test_generated_structs.py
index 1df9f0dc16368f..9a8102219d8769 100644
--- a/Lib/test/test_ctypes/test_generated_structs.py
+++ b/Lib/test/test_ctypes/test_generated_structs.py
@@ -10,16 +10,22 @@
"""
import unittest
-from test.support import import_helper
+from test.support import import_helper, verbose
import re
from dataclasses import dataclass
from functools import cached_property
+import sys
import ctypes
from ctypes import Structure, Union
from ctypes import sizeof, alignment, pointer, string_at
_ctypes_test = import_helper.import_module("_ctypes_test")
+from test.test_ctypes._support import StructCheckMixin
+
+# A 64-bit number where each nibble (hex digit) is different and
+# has 2-3 bits set.
+TEST_PATTERN = 0xae7596db
# ctypes erases the difference between `c_int` and e.g.`c_int16`.
# To keep it, we'll use custom subclasses with the C name stashed in `_c_name`:
@@ -426,7 +432,7 @@ class X(Structure):
_fields_ = [("_", X), ('y', c_byte)]
-class GeneratedTest(unittest.TestCase):
+class GeneratedTest(unittest.TestCase, StructCheckMixin):
def test_generated_data(self):
"""Check that a ctypes struct/union matches its C equivalent.
@@ -448,6 +454,7 @@ def test_generated_data(self):
"""
for name, cls in TESTCASES.items():
with self.subTest(name=name):
+ self.check_struct_or_union(cls)
if _maybe_skip := getattr(cls, '_maybe_skip', None):
_maybe_skip()
expected = iter(_ctypes_test.get_generated_test_data(name))
@@ -461,7 +468,7 @@ def test_generated_data(self):
obj = cls()
ptr = pointer(obj)
for field in iterfields(cls):
- for value in -1, 1, 0:
+ for value in -1, 1, TEST_PATTERN, 0:
with self.subTest(field=field.full_name, value=value):
field.set_to(obj, value)
py_mem = string_at(ptr, sizeof(obj))
@@ -472,6 +479,17 @@ def test_generated_data(self):
m = "\n".join([str(field), 'in:', *lines])
self.assertEqual(py_mem.hex(), c_mem.hex(), m)
+ descriptor = field.descriptor
+ field_mem = py_mem[
+ field.byte_offset
+ : field.byte_offset + descriptor.byte_size]
+ field_int = int.from_bytes(field_mem,
sys.byteorder)
+ mask = (1 << descriptor.bit_size) - 1
+ self.assertEqual(
+ (field_int >> descriptor.bit_offset) & mask,
+ value & mask)
+
+
# The rest of this file is generating C code from a ctypes type.
# This is only meant for (and tested with) the known inputs in this file!
@@ -569,6 +587,8 @@ class FieldInfo:
bits: int | None # number if this is a bit field
parent_type: type
parent: 'FieldInfo' #| None
+ descriptor: object
+ byte_offset: int
@cached_property
def attr_path(self):
@@ -600,10 +620,6 @@ def root(self):
else:
return self.parent
- @cached_property
- def descriptor(self):
- return getattr(self.parent_type, self.name)
-
def __repr__(self):
qname = f'{self.root.parent_type.__name__}.{self.full_name}'
try:
@@ -621,7 +637,11 @@ def iterfields(tp, parent=None):
else:
for fielddesc in fields:
f_name, f_tp, f_bits = unpack_field_desc(*fielddesc)
- sub = FieldInfo(f_name, f_tp, f_bits, tp, parent)
+ descriptor = getattr(tp, f_name)
+ byte_offset = descriptor.byte_offset
+ if parent:
+ byte_offset += parent.byte_offset
+ sub = FieldInfo(f_name, f_tp, f_bits, tp, parent, descriptor,
byte_offset)
yield from iterfields(f_tp, sub)
@@ -629,10 +649,9 @@ def iterfields(tp, parent=None):
# Dump C source to stdout
def output(string):
print(re.compile(r'^ +$', re.MULTILINE).sub('', string).lstrip('\n'))
+ output("/* Generated by Lib/test/test_ctypes/test_generated_structs.py */")
+ output(f"#define TEST_PATTERN {TEST_PATTERN}")
output("""
- /* Generated by Lib/test/test_ctypes/test_generated_structs.py */
-
-
// Append VALUE to the result.
#define APPEND(ITEM) { \\
PyObject *item = ITEM; \\
@@ -657,12 +676,13 @@ def output(string):
(char*)&value, sizeof(value))); \\
}
- // Set a field to -1, 1 and 0; append a snapshot of the memory
+ // Set a field to test values; append a snapshot of the memory
// after each of the operations.
- #define TEST_FIELD(TYPE, TARGET) { \\
- SET_AND_APPEND(TYPE, TARGET, -1) \\
- SET_AND_APPEND(TYPE, TARGET, 1) \\
- SET_AND_APPEND(TYPE, TARGET, 0) \\
+ #define TEST_FIELD(TYPE, TARGET) { \\
+ SET_AND_APPEND(TYPE, TARGET, -1) \\
+ SET_AND_APPEND(TYPE, TARGET, 1) \\
+ SET_AND_APPEND(TYPE, TARGET, (TYPE)TEST_PATTERN) \\
+ SET_AND_APPEND(TYPE, TARGET, 0) \\
}
#if defined(__GNUC__) || defined(__clang__)
diff --git a/Lib/test/test_ctypes/test_struct_fields.py
b/Lib/test/test_ctypes/test_struct_fields.py
index aafdb3582f2a25..5c713247a0f418 100644
--- a/Lib/test/test_ctypes/test_struct_fields.py
+++ b/Lib/test/test_ctypes/test_struct_fields.py
@@ -1,12 +1,12 @@
import unittest
import sys
-from ctypes import Structure, Union, sizeof, c_char, c_int
-from ._support import CField, Py_TPFLAGS_IMMUTABLETYPE
+from ctypes import Structure, Union, sizeof, c_char, c_int, CField
+from ._support import Py_TPFLAGS_IMMUTABLETYPE, StructCheckMixin
NOTHING = object()
-class FieldsTestBase:
+class FieldsTestBase(StructCheckMixin):
# Structure/Union classes must get 'finalized' sooner or
# later, when one of these things happen:
#
@@ -78,25 +78,48 @@ class Subclass(BrokenStructure): ...
def test_max_field_size_gh126937(self):
# Classes for big structs should be created successfully.
# (But they most likely can't be instantiated.)
- # Here we test the exact limit: the number of *bits* must fit
- # in Py_ssize_t.
+ # The size must fit in Py_ssize_t.
- class X(self.cls):
+ max_field_size = sys.maxsize
+
+ class X(Structure):
_fields_ = [('char', c_char),]
- max_field_size = sys.maxsize // 8
+ self.check_struct(X)
- class Y(self.cls):
+ class Y(Structure):
_fields_ = [('largeField', X * max_field_size)]
- class Z(self.cls):
+ self.check_struct(Y)
+
+ class Z(Structure):
_fields_ = [('largeField', c_char * max_field_size)]
+ self.check_struct(Z)
- with self.assertRaises(ValueError):
- class TooBig(self.cls):
+ # The *bit* size overflows Py_ssize_t.
+ self.assertEqual(Y.largeField.bit_size, max_field_size * 8)
+ self.assertEqual(Z.largeField.bit_size, max_field_size * 8)
+
+ self.assertEqual(Y.largeField.byte_size, max_field_size)
+ self.assertEqual(Z.largeField.byte_size, max_field_size)
+ self.assertEqual(sizeof(Y), max_field_size)
+ self.assertEqual(sizeof(Z), max_field_size)
+
+ with self.assertRaises(OverflowError):
+ class TooBig(Structure):
_fields_ = [('largeField', X * (max_field_size + 1))]
- with self.assertRaises(ValueError):
- class TooBig(self.cls):
+ with self.assertRaises(OverflowError):
+ class TooBig(Structure):
_fields_ = [('largeField', c_char * (max_field_size + 1))]
+ # Also test around edge case for the bit_size calculation
+ for size in (max_field_size // 8 - 1,
+ max_field_size // 8,
+ max_field_size // 8 + 1):
+ class S(Structure):
+ _fields_ = [('largeField', c_char * size),]
+ self.check_struct(S)
+ self.assertEqual(S.largeField.bit_size, size * 8)
+
+
# __set__ and __get__ should raise a TypeError in case their self
# argument is not a ctype instance.
def test___set__(self):
diff --git a/Lib/test/test_ctypes/test_structunion.py
b/Lib/test/test_ctypes/test_structunion.py
index 973ac3b2f1919d..8d8b7e5e995132 100644
--- a/Lib/test/test_ctypes/test_structunion.py
+++ b/Lib/test/test_ctypes/test_structunion.py
@@ -1,10 +1,12 @@
"""Common tests for ctypes.Structure and ctypes.Union"""
import unittest
+import sys
from ctypes import (Structure, Union, POINTER, sizeof, alignment,
c_char, c_byte, c_ubyte,
c_short, c_ushort, c_int, c_uint,
- c_long, c_ulong, c_longlong, c_ulonglong, c_float,
c_double)
+ c_long, c_ulong, c_longlong, c_ulonglong, c_float,
c_double,
+ c_int8, c_int16, c_int32)
from ._support import (_CData, PyCStructType, UnionType,
Py_TPFLAGS_DISALLOW_INSTANTIATION,
Py_TPFLAGS_IMMUTABLETYPE)
@@ -175,6 +177,102 @@ class X(self.cls):
# XXX Should we check nested data types also?
# offset is always relative to the class...
+ def test_field_descriptor_attributes(self):
+ """Test information provided by the descriptors"""
+ class Inner(Structure):
+ _fields_ = [
+ ("a", c_int16),
+ ("b", c_int8, 1),
+ ("c", c_int8, 2),
+ ]
+ class X(self.cls):
+ _fields_ = [
+ ("x", c_int32),
+ ("y", c_int16, 1),
+ ("_", Inner),
+ ]
+ _anonymous_ = ["_"]
+
+ field_names = "xy_abc"
+
+ # name
+
+ for name in field_names:
+ with self.subTest(name=name):
+ self.assertEqual(getattr(X, name).name, name)
+
+ # type
+
+ expected_types = dict(
+ x=c_int32,
+ y=c_int16,
+ _=Inner,
+ a=c_int16,
+ b=c_int8,
+ c=c_int8,
+ )
+ assert set(expected_types) == set(field_names)
+ for name, tp in expected_types.items():
+ with self.subTest(name=name):
+ self.assertEqual(getattr(X, name).type, tp)
+ self.assertEqual(getattr(X, name).byte_size, sizeof(tp))
+
+ # offset, byte_offset
+
+ expected_offsets = dict(
+ x=(0, 0),
+ y=(0, 4),
+ _=(0, 6),
+ a=(0, 6),
+ b=(2, 8),
+ c=(2, 8),
+ )
+ assert set(expected_offsets) == set(field_names)
+ for name, (union_offset, struct_offset) in expected_offsets.items():
+ with self.subTest(name=name):
+ self.assertEqual(getattr(X, name).offset,
+ getattr(X, name).byte_offset)
+ if self.cls == Structure:
+ self.assertEqual(getattr(X, name).offset, struct_offset)
+ else:
+ self.assertEqual(getattr(X, name).offset, union_offset)
+
+ # is_bitfield, bit_size, bit_offset
+ # size
+
+ little_endian = (sys.byteorder == 'little')
+ expected_bitfield_info = dict(
+ # (bit_size, bit_offset)
+ b=(1, 0 if little_endian else 7),
+ c=(2, 1 if little_endian else 5),
+ y=(1, 0 if little_endian else 15),
+ )
+ for name in field_names:
+ with self.subTest(name=name):
+ if info := expected_bitfield_info.get(name):
+ self.assertEqual(getattr(X, name).is_bitfield, True)
+ expected_bit_size, expected_bit_offset = info
+ self.assertEqual(getattr(X, name).bit_size,
+ expected_bit_size)
+ self.assertEqual(getattr(X, name).bit_offset,
+ expected_bit_offset)
+ self.assertEqual(getattr(X, name).size,
+ (expected_bit_size << 16)
+ | expected_bit_offset)
+ else:
+ self.assertEqual(getattr(X, name).is_bitfield, False)
+ type_size = sizeof(expected_types[name])
+ self.assertEqual(getattr(X, name).bit_size, type_size * 8)
+ self.assertEqual(getattr(X, name).bit_offset, 0)
+ self.assertEqual(getattr(X, name).size, type_size)
+
+ # is_anonymous
+
+ for name in field_names:
+ with self.subTest(name=name):
+ self.assertEqual(getattr(X, name).is_anonymous, (name == '_'))
+
+
def test_invalid_field_types(self):
class POINT(self.cls):
pass
@@ -182,11 +280,19 @@ class POINT(self.cls):
def test_invalid_name(self):
# field name must be string
- def declare_with_name(name):
- class S(self.cls):
- _fields_ = [(name, c_int)]
-
- self.assertRaises(TypeError, declare_with_name, b"x")
+ for name in b"x", 3, None:
+ with self.subTest(name=name):
+ with self.assertRaises(TypeError):
+ class S(self.cls):
+ _fields_ = [(name, c_int)]
+
+ def test_str_name(self):
+ class WeirdString(str):
+ def __str__(self):
+ return "unwanted value"
+ class S(self.cls):
+ _fields_ = [(WeirdString("f"), c_int)]
+ self.assertEqual(S.f.name, "f")
def test_intarray_fields(self):
class SomeInts(self.cls):
diff --git a/Lib/test/test_ctypes/test_structures.py
b/Lib/test/test_ctypes/test_structures.py
index 0ec238e04b74cd..67616086b1907c 100644
--- a/Lib/test/test_ctypes/test_structures.py
+++ b/Lib/test/test_ctypes/test_structures.py
@@ -15,15 +15,17 @@
from collections import namedtuple
from test import support
from test.support import import_helper
+from ._support import StructCheckMixin
_ctypes_test = import_helper.import_module("_ctypes_test")
-class StructureTestCase(unittest.TestCase):
+class StructureTestCase(unittest.TestCase, StructCheckMixin):
def test_packed(self):
class X(Structure):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 1
+ self.check_struct(X)
self.assertEqual(sizeof(X), 9)
self.assertEqual(X.b.offset, 1)
@@ -32,6 +34,7 @@ class X(Structure):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 2
+ self.check_struct(X)
self.assertEqual(sizeof(X), 10)
self.assertEqual(X.b.offset, 2)
@@ -42,6 +45,7 @@ class X(Structure):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 4
+ self.check_struct(X)
self.assertEqual(sizeof(X), min(4, longlong_align) + longlong_size)
self.assertEqual(X.b.offset, min(4, longlong_align))
@@ -49,6 +53,7 @@ class X(Structure):
_fields_ = [("a", c_byte),
("b", c_longlong)]
_pack_ = 8
+ self.check_struct(X)
self.assertEqual(sizeof(X), min(8, longlong_align) + longlong_size)
self.assertEqual(X.b.offset, min(8, longlong_align))
@@ -89,6 +94,7 @@ class Person(Structure):
def test_conflicting_initializers(self):
class POINT(Structure):
_fields_ = [("phi", c_float), ("rho", c_float)]
+ self.check_struct(POINT)
# conflicting positional and keyword args
self.assertRaisesRegex(TypeError, "phi", POINT, 2, 3, phi=4)
self.assertRaisesRegex(TypeError, "rho", POINT, 2, 3, rho=4)
@@ -99,6 +105,7 @@ class POINT(Structure):
def test_keyword_initializers(self):
class POINT(Structure):
_fields_ = [("x", c_int), ("y", c_int)]
+ self.check_struct(POINT)
pt = POINT(1, 2)
self.assertEqual((pt.x, pt.y), (1, 2))
@@ -110,11 +117,13 @@ def test_nested_initializers(self):
class Phone(Structure):
_fields_ = [("areacode", c_char*6),
("number", c_char*12)]
+ self.check_struct(Phone)
class Person(Structure):
_fields_ = [("name", c_char * 12),
("phone", Phone),
("age", c_int)]
+ self.check_struct(Person)
p = Person(b"Someone", (b"1234", b"5678"), 5)
@@ -127,6 +136,7 @@ def test_structures_with_wchar(self):
class PersonW(Structure):
_fields_ = [("name", c_wchar * 12),
("age", c_int)]
+ self.check_struct(PersonW)
p = PersonW("Someone \xe9")
self.assertEqual(p.name, "Someone \xe9")
@@ -142,11 +152,13 @@ def test_init_errors(self):
class Phone(Structure):
_fields_ = [("areacode", c_char*6),
("number", c_char*12)]
+ self.check_struct(Phone)
class Person(Structure):
_fields_ = [("name", c_char * 12),
("phone", Phone),
("age", c_int)]
+ self.check_struct(Person)
cls, msg = self.get_except(Person, b"Someone", (1, 2))
self.assertEqual(cls, RuntimeError)
@@ -169,12 +181,19 @@ def test_positional_args(self):
# see also http://bugs.python.org/issue5042
class W(Structure):
_fields_ = [("a", c_int), ("b", c_int)]
+ self.check_struct(W)
+
class X(W):
_fields_ = [("c", c_int)]
+ self.check_struct(X)
+
class Y(X):
pass
+ self.check_struct(Y)
+
class Z(Y):
_fields_ = [("d", c_int), ("e", c_int), ("f", c_int)]
+ self.check_struct(Z)
z = Z(1, 2, 3, 4, 5, 6)
self.assertEqual((z.a, z.b, z.c, z.d, z.e, z.f),
@@ -193,6 +212,7 @@ class Test(Structure):
('second', c_ulong),
('third', c_ulong),
]
+ self.check_struct(Test)
s = Test()
s.first = 0xdeadbeef
@@ -222,6 +242,7 @@ class Test(Structure):
]
def __del__(self):
finalizer_calls.append("called")
+ self.check_struct(Test)
s = Test(1, 2, 3)
# Test the StructUnionType_paramfunc() code path which copies the
@@ -251,6 +272,7 @@ class X(Structure):
('first', c_uint),
('second', c_uint)
]
+ self.check_struct(X)
s = X()
s.first = 0xdeadbeef
@@ -339,36 +361,43 @@ class Test2(Structure):
_fields_ = [
('data', c_ubyte * 16),
]
+ self.check_struct(Test2)
class Test3AParent(Structure):
_fields_ = [
('data', c_float * 2),
]
+ self.check_struct(Test3AParent)
class Test3A(Test3AParent):
_fields_ = [
('more_data', c_float * 2),
]
+ self.check_struct(Test3A)
class Test3B(Structure):
_fields_ = [
('data', c_double * 2),
]
+ self.check_struct(Test3B)
class Test3C(Structure):
_fields_ = [
("data", c_double * 4)
]
+ self.check_struct(Test3C)
class Test3D(Structure):
_fields_ = [
("data", c_double * 8)
]
+ self.check_struct(Test3D)
class Test3E(Structure):
_fields_ = [
("data", c_double * 9)
]
+ self.check_struct(Test3E)
# Tests for struct Test2
@@ -467,6 +496,8 @@ class U(Union):
('f2', c_uint16 * 8),
('f3', c_uint32 * 4),
]
+ self.check_union(U)
+
u = U()
u.f3[0] = 0x01234567
u.f3[1] = 0x89ABCDEF
@@ -493,18 +524,21 @@ class Nested1(Structure):
('an_int', c_int),
('another_int', c_int),
]
+ self.check_struct(Nested1)
class Test4(Union):
_fields_ = [
('a_long', c_long),
('a_struct', Nested1),
]
+ self.check_struct(Test4)
class Nested2(Structure):
_fields_ = [
('an_int', c_int),
('a_union', Test4),
]
+ self.check_struct(Nested2)
class Test5(Structure):
_fields_ = [
@@ -512,6 +546,7 @@ class Test5(Structure):
('nested', Nested2),
('another_int', c_int),
]
+ self.check_struct(Test5)
test4 = Test4()
dll = CDLL(_ctypes_test.__file__)
@@ -576,6 +611,7 @@ class Test6(Structure):
('C', c_int, 3),
('D', c_int, 2),
]
+ self.check_struct(Test6)
test6 = Test6()
# As these are signed int fields, all are logically -1 due to sign
@@ -611,6 +647,8 @@ class Test7(Structure):
('C', c_uint, 3),
('D', c_uint, 2),
]
+ self.check_struct(Test7)
+
test7 = Test7()
test7.A = 1
test7.B = 3
@@ -634,6 +672,7 @@ class Test8(Union):
('C', c_int, 3),
('D', c_int, 2),
]
+ self.check_union(Test8)
test8 = Test8()
with self.assertRaises(TypeError) as ctx:
diff --git
a/Misc/NEWS.d/next/Library/2025-01-17-17-35-16.gh-issue-128715.tQjo89.rst
b/Misc/NEWS.d/next/Library/2025-01-17-17-35-16.gh-issue-128715.tQjo89.rst
new file mode 100644
index 00000000000000..5ca6250795a44f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-17-17-35-16.gh-issue-128715.tQjo89.rst
@@ -0,0 +1,3 @@
+The class of :class:`~ctypes.Structure`/:class:`~ctypes.Union` field
+descriptors is now available as :class:`~ctypes.CField`, and has new
+attributes to aid debugging and introspection.
diff --git a/Modules/_ctypes/_ctypes_test_generated.c.h
b/Modules/_ctypes/_ctypes_test_generated.c.h
index d70b33eaa8b515..8bcbc770f21cdf 100644
--- a/Modules/_ctypes/_ctypes_test_generated.c.h
+++ b/Modules/_ctypes/_ctypes_test_generated.c.h
@@ -1,6 +1,5 @@
- /* Generated by Lib/test/test_ctypes/test_generated_structs.py */
-
-
+/* Generated by Lib/test/test_ctypes/test_generated_structs.py */
+#define TEST_PATTERN 2926941915
// Append VALUE to the result.
#define APPEND(ITEM) { \
PyObject *item = ITEM; \
@@ -25,12 +24,13 @@
(char*)&value, sizeof(value))); \
}
- // Set a field to -1, 1 and 0; append a snapshot of the memory
+ // Set a field to test values; append a snapshot of the memory
// after each of the operations.
- #define TEST_FIELD(TYPE, TARGET) { \
- SET_AND_APPEND(TYPE, TARGET, -1) \
- SET_AND_APPEND(TYPE, TARGET, 1) \
- SET_AND_APPEND(TYPE, TARGET, 0) \
+ #define TEST_FIELD(TYPE, TARGET) { \
+ SET_AND_APPEND(TYPE, TARGET, -1) \
+ SET_AND_APPEND(TYPE, TARGET, 1) \
+ SET_AND_APPEND(TYPE, TARGET, (TYPE)TEST_PATTERN) \
+ SET_AND_APPEND(TYPE, TARGET, 0) \
}
#if defined(__GNUC__) || defined(__clang__)
diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c
index 6dd6f6ec56d008..ec113e41d16323 100644
--- a/Modules/_ctypes/callbacks.c
+++ b/Modules/_ctypes/callbacks.c
@@ -11,8 +11,6 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_runtime.h" // _Py_ID()
-#include <stdbool.h>
-
#ifdef MS_WIN32
# include <malloc.h>
#endif
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index c6b6460126ca90..c652634a137431 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -66,8 +66,6 @@ module _ctypes
#include "Python.h"
-#include <stdbool.h>
-
#ifdef MS_WIN32
#include <windows.h>
#include <tchar.h>
diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c
index 9924d62c0881d1..7086ba2010607d 100644
--- a/Modules/_ctypes/cfield.c
+++ b/Modules/_ctypes/cfield.c
@@ -10,7 +10,6 @@
#include "pycore_bitutils.h" // _Py_bswap32()
#include "pycore_call.h" // _PyObject_CallNoArgs()
-#include <stdbool.h> // bool
#include <ffi.h>
#include "ctypes.h"
@@ -56,71 +55,56 @@ Py_ssize_t LOW_BIT(Py_ssize_t offset);
@classmethod
_ctypes.CField.__new__ as PyCField_new
+ *
name: object(subclass_of='&PyUnicode_Type')
type as proto: object
- size: Py_ssize_t
- offset: Py_ssize_t
+ byte_size: Py_ssize_t
+ byte_offset: Py_ssize_t
index: Py_ssize_t
+ _internal_use: bool
bit_size as bit_size_obj: object = None
+ bit_offset as bit_offset_obj: object = None
[clinic start generated code]*/
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]*/
+ Py_ssize_t byte_size, Py_ssize_t byte_offset,
+ Py_ssize_t index, int _internal_use,
+ PyObject *bit_size_obj, PyObject *bit_offset_obj)
+/*[clinic end generated code: output=3f2885ee4108b6e2 input=b343436e33c0d782]*/
{
CFieldObject* self = NULL;
- if (size < 0) {
- PyErr_Format(PyExc_ValueError,
- "size of field %R must not be negative, got %zd",
- name, size);
+
+ if (!_internal_use) {
+ // Do not instantiate outside ctypes, yet.
+ // The constructor is internal API and may change without warning.
+ PyErr_Format(PyExc_TypeError, "cannot create %T object", type);
goto error;
}
- // assert: no overflow;
- if ((unsigned long long int) size
- >= (1ULL << (8*sizeof(Py_ssize_t)-1)) / 8) {
+ if (byte_size < 0) {
PyErr_Format(PyExc_ValueError,
- "size of field %R is too big: %zd", name, size);
+ "byte size of field %R must not be negative, got %zd",
+ name, byte_size);
goto error;
}
- PyTypeObject *tp = type;
- ctypes_state *st = get_module_state_by_class(tp);
- self = (CFieldObject *)tp->tp_alloc(tp, 0);
- if (!self) {
- return NULL;
- }
- if (PyUnicode_CheckExact(name)) {
- self->name = Py_NewRef(name);
- } else {
- self->name = PyObject_Str(name);
- if (!self->name) {
- goto error;
- }
- }
-
+ ctypes_state *st = get_module_state_by_class(type);
StgInfo *info;
if (PyStgInfo_FromType(st, proto, &info) < 0) {
goto error;
}
if (info == NULL) {
PyErr_Format(PyExc_TypeError,
- "type of field %R must be a C type", self->name);
+ "type of field %R must be a C type", name);
goto error;
}
+ assert(byte_size == info->size);
+ Py_ssize_t bitfield_size = 0;
+ Py_ssize_t bit_offset = 0;
if (bit_size_obj != Py_None) {
-#ifdef Py_DEBUG
- Py_ssize_t bit_size = NUM_BITS(size);
- assert(bit_size > 0);
- assert(bit_size <= info->size * 8);
- // Currently, the bit size is specified redundantly
- // in NUM_BITS(size) and bit_size_obj.
- // Verify that they match.
- assert(PyLong_AsSsize_t(bit_size_obj) == bit_size);
-#endif
+ // It's a bit field!
switch(info->ffi_type_pointer.type) {
case FFI_TYPE_UINT8:
case FFI_TYPE_UINT16:
@@ -144,11 +128,67 @@ PyCField_new_impl(PyTypeObject *type, PyObject *name,
PyObject *proto,
((PyTypeObject*)proto)->tp_name);
goto error;
}
+
+ if (byte_size > 100) {
+ // Bitfields must "live" in a field defined by a ffi type,
+ // so they're limited to about 8 bytes.
+ // This check is here to avoid overflow in later checks.
+ PyErr_Format(PyExc_ValueError,
+ "bit field %R size too large, got %zd",
+ name, byte_size);
+ goto error;
+ }
+ bitfield_size = PyLong_AsSsize_t(bit_size_obj);
+ if ((bitfield_size <= 0) || (bitfield_size > 255)) {
+ if (!PyErr_Occurred()) {
+ PyErr_Format(PyExc_ValueError,
+ "bit size of field %R out of range, got %zd",
+ name, bitfield_size);
+ }
+ goto error;
+ }
+ bit_offset = PyLong_AsSsize_t(bit_offset_obj);
+ if ((bit_offset < 0) || (bit_offset > 255)) {
+ if (!PyErr_Occurred()) {
+ PyErr_Format(PyExc_ValueError,
+ "bit offset of field %R out of range, got %zd",
+ name, bit_offset);
+ }
+ goto error;
+ }
+ if ((bitfield_size + bit_offset) > byte_size * 8) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "bit field %R overflows its type (%zd + %zd >= %zd)",
+ name, bit_offset, byte_size*8);
+ goto error;
+ }
+ }
+ else {
+ if (bit_offset_obj != Py_None) {
+ PyErr_Format(
+ PyExc_ValueError,
+ "field %R: bit_offset must be specified if bit_size is",
+ name);
+ goto error;
+ }
+ }
+
+ self = _CFieldObject_CAST(type->tp_alloc(type, 0));
+ if (!self) {
+ return NULL;
+ }
+ self->name = PyUnicode_FromObject(name);
+ if (!self->name) {
+ goto error;
}
+ assert(PyUnicode_CheckExact(self->name));
self->proto = Py_NewRef(proto);
- self->size = size;
- self->offset = offset;
+ self->byte_size = byte_size;
+ self->byte_offset = byte_offset;
+ self->bitfield_size = (uint8_t)bitfield_size;
+ self->bit_offset = (uint8_t)bit_offset;
self->index = index;
@@ -192,6 +232,15 @@ PyCField_new_impl(PyTypeObject *type, PyObject *name,
PyObject *proto,
return NULL;
}
+static inline Py_ssize_t
+_pack_legacy_size(CFieldObject *field)
+{
+ if (field->bitfield_size) {
+ Py_ssize_t bit_offset = field->bit_offset;
+ return (field->bitfield_size << 16) | bit_offset;
+ }
+ return field->byte_size;
+}
static int
PyCField_set(PyObject *op, PyObject *inst, PyObject *value)
@@ -206,14 +255,14 @@ PyCField_set(PyObject *op, PyObject *inst, PyObject
*value)
return -1;
}
dst = _CDataObject_CAST(inst);
- ptr = dst->b_ptr + self->offset;
+ ptr = dst->b_ptr + self->byte_offset;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError,
"can't delete attribute");
return -1;
}
return PyCData_set(st, inst, self->proto, self->setfunc, value,
- self->index, self->size, ptr);
+ self->index, _pack_legacy_size(self), ptr);
}
static PyObject *
@@ -232,25 +281,110 @@ PyCField_get(PyObject *op, PyObject *inst, PyTypeObject
*type)
}
src = _CDataObject_CAST(inst);
return PyCData_get(st, self->proto, self->getfunc, inst,
- self->index, self->size, src->b_ptr + self->offset);
+ self->index, _pack_legacy_size(self),
+ src->b_ptr + self->byte_offset);
}
static PyObject *
-PyCField_get_offset(PyObject *self, void *data)
+PyCField_get_legacy_size(PyObject *self, void *Py_UNUSED(closure))
{
- return PyLong_FromSsize_t(_CFieldObject_CAST(self)->offset);
+ CFieldObject *field = _CFieldObject_CAST(self);
+ return PyLong_FromSsize_t(_pack_legacy_size(field));
}
static PyObject *
-PyCField_get_size(PyObject *self, void *data)
+PyCField_get_bit_size(PyObject *self, void *Py_UNUSED(closure))
{
- return PyLong_FromSsize_t(_CFieldObject_CAST(self)->size);
+ CFieldObject *field = _CFieldObject_CAST(self);
+ if (field->bitfield_size) {
+ return PyLong_FromSsize_t(field->bitfield_size);
+ }
+ if (field->byte_size < PY_SSIZE_T_MAX / 8) {
+ return PyLong_FromSsize_t(field->byte_size * 8);
+ }
+
+ // If the bit size overflows Py_ssize_t, we don't try fitting it in
+ // a bigger C type. Use Python ints.
+ PyObject *byte_size_obj = NULL;
+ PyObject *eight = NULL;
+ PyObject *result = NULL;
+
+ byte_size_obj = PyLong_FromSsize_t(field->byte_size);
+ if (!byte_size_obj) {
+ goto finally;
+ }
+ eight = PyLong_FromLong(8);
+ if (!eight) {
+ goto finally;
+ }
+ result = PyNumber_Multiply(byte_size_obj, eight);
+finally:
+ Py_XDECREF(byte_size_obj);
+ Py_XDECREF(eight);
+ return result;
+}
+
+static PyObject *
+PyCField_is_bitfield(PyObject *self, void *Py_UNUSED(closure))
+{
+ return PyBool_FromLong(_CFieldObject_CAST(self)->bitfield_size);
+}
+
+static PyObject *
+PyCField_is_anonymous(PyObject *self, void *Py_UNUSED(closure))
+{
+ return PyBool_FromLong(_CFieldObject_CAST(self)->anonymous);
}
static PyGetSetDef PyCField_getset[] = {
- { "offset", PyCField_get_offset, NULL, PyDoc_STR("offset in bytes of this
field") },
- { "size", PyCField_get_size, NULL, PyDoc_STR("size in bytes of this
field") },
- { NULL, NULL, NULL, NULL },
+ { "size", PyCField_get_legacy_size, NULL,
+ PyDoc_STR("size in bytes of this field. For bitfields, this is a "
+ "legacy packed value; use byte_size instead") },
+
+ { "bit_size", PyCField_get_bit_size, NULL,
+ PyDoc_STR("size of this field in bits") },
+ { "is_bitfield", PyCField_is_bitfield, NULL,
+ PyDoc_STR("true if this is a bitfield") },
+ { "is_anonymous", PyCField_is_anonymous, NULL,
+ PyDoc_STR("true if this field is anonymous") },
+ { NULL },
+};
+
+static PyMemberDef PyCField_members[] = {
+ { "name",
+ .type = Py_T_OBJECT_EX,
+ .offset = offsetof(CFieldObject, name),
+ .flags = Py_READONLY,
+ .doc = PyDoc_STR("name of this field") },
+ { "type",
+ .type = Py_T_OBJECT_EX,
+ .offset = offsetof(CFieldObject, proto),
+ .flags = Py_READONLY,
+ .doc = PyDoc_STR("type of this field") },
+ { "offset",
+ .type = Py_T_PYSSIZET,
+ .offset = offsetof(CFieldObject, byte_offset),
+ .flags = Py_READONLY,
+ .doc = PyDoc_STR(
+ "offset in bytes of this field (same as byte_offset)") },
+ { "byte_offset",
+ .type = Py_T_PYSSIZET,
+ .offset = offsetof(CFieldObject, byte_offset),
+ .flags = Py_READONLY,
+ .doc = PyDoc_STR("offset in bytes of this field. "
+ "For bitfields: excludes bit_offset.") },
+ { "byte_size",
+ .type = Py_T_PYSSIZET,
+ .offset = offsetof(CFieldObject, byte_size),
+ .flags = Py_READONLY,
+ .doc = PyDoc_STR("size of this field in bytes") },
+ { "bit_offset",
+ .type = Py_T_UBYTE,
+ .offset = offsetof(CFieldObject, bit_offset),
+ .flags = Py_READONLY,
+ .doc = PyDoc_STR("additional offset in bits (relative to byte_offset);"
+ " zero for non-bitfields") },
+ { NULL },
};
static int
@@ -282,24 +416,27 @@ PyCField_dealloc(PyObject *self)
}
static PyObject *
-PyCField_repr(PyObject *op)
+PyCField_repr(PyObject *self)
{
+ CFieldObject *field = _CFieldObject_CAST(self);
PyObject *result;
- CFieldObject *self = _CFieldObject_CAST(op);
- Py_ssize_t bits = NUM_BITS(self->size);
- Py_ssize_t size = LOW_BIT(self->size);
- const char *name;
-
- name = ((PyTypeObject *)self->proto)->tp_name;
+ const char *tp_name = ((PyTypeObject *)field->proto)->tp_name;
- if (bits)
+ if (field->bitfield_size) {
result = PyUnicode_FromFormat(
- "<Field type=%s, ofs=%zd:%zd, bits=%zd>",
- name, self->offset, size, bits);
- else
+ "<%T %R type=%s, ofs=%zd, bit_size=%zd, bit_offset=%zd>",
+ self,
+ field->name, tp_name, field->byte_offset,
+ (Py_ssize_t)field->bitfield_size,
+ (Py_ssize_t)field->bit_offset);
+ }
+ else {
result = PyUnicode_FromFormat(
- "<Field type=%s, ofs=%zd, size=%zd>",
- name, self->offset, size);
+ "<%T %R type=%s, ofs=%zd, size=%zd>",
+ self,
+ field->name, tp_name, field->byte_offset,
+ field->byte_size);
+ }
return result;
}
@@ -311,13 +448,14 @@ static PyType_Slot cfield_slots[] = {
{Py_tp_traverse, PyCField_traverse},
{Py_tp_clear, PyCField_clear},
{Py_tp_getset, PyCField_getset},
+ {Py_tp_members, PyCField_members},
{Py_tp_descr_get, PyCField_get},
{Py_tp_descr_set, PyCField_set},
{0, NULL},
};
PyType_Spec cfield_spec = {
- .name = "_ctypes.CField",
+ .name = "ctypes.CField",
.basicsize = sizeof(CFieldObject),
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_IMMUTABLETYPE),
diff --git a/Modules/_ctypes/clinic/cfield.c.h
b/Modules/_ctypes/clinic/cfield.c.h
index bbf108a1a07b67..3d16139f2d614a 100644
--- a/Modules/_ctypes/clinic/cfield.c.h
+++ b/Modules/_ctypes/clinic/cfield.c.h
@@ -11,8 +11,9 @@ preserve
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);
+ Py_ssize_t byte_size, Py_ssize_t byte_offset,
+ Py_ssize_t index, int _internal_use,
+ PyObject *bit_size_obj, PyObject *bit_offset_obj);
static PyObject *
PyCField_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
@@ -20,14 +21,14 @@ 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
+ #define NUM_KEYWORDS 8
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), },
+ .ob_item = { &_Py_ID(name), &_Py_ID(type), &_Py_ID(byte_size),
&_Py_ID(byte_offset), &_Py_ID(index), &_Py_ID(_internal_use),
&_Py_ID(bit_size), &_Py_ID(bit_offset), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -36,26 +37,28 @@ PyCField_new(PyTypeObject *type, PyObject *args, PyObject
*kwargs)
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"name", "type", "size", "offset",
"index", "bit_size", NULL};
+ static const char * const _keywords[] = {"name", "type", "byte_size",
"byte_offset", "index", "_internal_use", "bit_size", "bit_offset", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "CField",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[6];
+ PyObject *argsbuf[8];
PyObject * const *fastargs;
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
- Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 5;
+ Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 6;
PyObject *name;
PyObject *proto;
- Py_ssize_t size;
- Py_ssize_t offset;
+ Py_ssize_t byte_size;
+ Py_ssize_t byte_offset;
Py_ssize_t index;
+ int _internal_use;
PyObject *bit_size_obj = Py_None;
+ PyObject *bit_offset_obj = Py_None;
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs,
kwargs, NULL, &_parser,
- /*minpos*/ 5, /*maxpos*/ 6, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 6, /*varpos*/ 0, argsbuf);
if (!fastargs) {
goto exit;
}
@@ -75,7 +78,7 @@ PyCField_new(PyTypeObject *type, PyObject *args, PyObject
*kwargs)
if (ival == -1 && PyErr_Occurred()) {
goto exit;
}
- size = ival;
+ byte_size = ival;
}
{
Py_ssize_t ival = -1;
@@ -87,7 +90,7 @@ PyCField_new(PyTypeObject *type, PyObject *args, PyObject
*kwargs)
if (ival == -1 && PyErr_Occurred()) {
goto exit;
}
- offset = ival;
+ byte_offset = ival;
}
{
Py_ssize_t ival = -1;
@@ -101,14 +104,24 @@ PyCField_new(PyTypeObject *type, PyObject *args, PyObject
*kwargs)
}
index = ival;
}
+ _internal_use = PyObject_IsTrue(fastargs[5]);
+ if (_internal_use < 0) {
+ goto exit;
+ }
if (!noptargs) {
- goto skip_optional_pos;
+ goto skip_optional_kwonly;
+ }
+ if (fastargs[6]) {
+ bit_size_obj = fastargs[6];
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
}
- bit_size_obj = fastargs[5];
-skip_optional_pos:
- return_value = PyCField_new_impl(type, name, proto, size, offset, index,
bit_size_obj);
+ bit_offset_obj = fastargs[7];
+skip_optional_kwonly:
+ return_value = PyCField_new_impl(type, name, proto, byte_size,
byte_offset, index, _internal_use, bit_size_obj, bit_offset_obj);
exit:
return return_value;
}
-/*[clinic end generated code: output=6b450bdd861571e7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=7160ded221fb00ff input=a9049054013a1b77]*/
diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h
index 07049d0968c790..2b8192059a0dc2 100644
--- a/Modules/_ctypes/ctypes.h
+++ b/Modules/_ctypes/ctypes.h
@@ -2,6 +2,8 @@
# include <alloca.h>
#endif
+#include <stdbool.h>
+
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
@@ -280,14 +282,36 @@ extern char *_ctypes_get_simple_type_chars(void);
typedef struct CFieldObject {
PyObject_HEAD
- Py_ssize_t offset;
- Py_ssize_t size;
- Py_ssize_t index; /* Index into CDataObject's
- object array */
+
+ /* byte size & offset
+ * For bit fields, this identifies a chunk of memory that the bits are
+ * extracted from. The entire chunk needs to be contained in the enclosing
+ * struct/union.
+ * byte_size is the same as the underlying ctype size (and thus it is
+ * redundant and could be eliminated).
+ * Note that byte_offset might not be aligned to proto's alignment.
+ */
+ Py_ssize_t byte_offset;
+ Py_ssize_t byte_size;
+
+ Py_ssize_t index; /* Index into CDataObject's object
array */
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;
+ bool anonymous: 1;
+
+ /* If this is a bit field, bitfield_size must be positive.
+ * bitfield_size and bit_offset specify the field inside the chunk of
+ * memory identified by byte_offset & byte_size.
+ * Otherwise, these are both zero.
+ *
+ * Note that for NON-bitfields:
+ * - `bit_size` (user-facing Python attribute) `is byte_size*8`
+ * - `bitfield_size` (this) is zero
+ * Hence the different name.
+ */
+ uint8_t bitfield_size;
+ uint8_t bit_offset;
PyObject *name; /* exact PyUnicode */
} CFieldObject;
diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c
index 31b83374917152..6e4f8eb21d9632 100644
--- a/Modules/_ctypes/stgdict.c
+++ b/Modules/_ctypes/stgdict.c
@@ -120,7 +120,7 @@ MakeFields(PyObject *type, CFieldObject *descr,
if (fdescr->anonymous) {
int rc = MakeFields(type, fdescr,
index + fdescr->index,
- offset + fdescr->offset);
+ offset + fdescr->byte_offset);
Py_DECREF(fdescr);
if (rc == -1) {
Py_DECREF(fieldlist);
@@ -135,12 +135,16 @@ MakeFields(PyObject *type, CFieldObject *descr,
return -1;
}
assert(Py_IS_TYPE(new_descr, cfield_tp));
- new_descr->size = fdescr->size;
- new_descr->offset = fdescr->offset + offset;
+ new_descr->byte_size = fdescr->byte_size;
+ new_descr->byte_offset = fdescr->byte_offset + offset;
+ new_descr->bitfield_size = fdescr->bitfield_size;
+ new_descr->bit_offset = fdescr->bit_offset;
new_descr->index = fdescr->index + index;
new_descr->proto = Py_XNewRef(fdescr->proto);
new_descr->getfunc = fdescr->getfunc;
new_descr->setfunc = fdescr->setfunc;
+ new_descr->name = Py_NewRef(fdescr->name);
+ new_descr->anonymous = fdescr->anonymous;
Py_DECREF(fdescr);
@@ -198,7 +202,7 @@ MakeAnonFields(PyObject *type)
/* descr is in the field descriptor. */
if (-1 == MakeFields(type, (CFieldObject *)descr,
((CFieldObject *)descr)->index,
- ((CFieldObject *)descr)->offset)) {
+ ((CFieldObject *)descr)->byte_offset)) {
Py_DECREF(descr);
Py_DECREF(anon_names);
return -1;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]