From a6ef3ac10ad2cc5f75244332b9c989bb0cb20314 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Sun, 7 Jan 2024 10:34:59 +1100 Subject: [PATCH 1/8] Add optional _align_ attribute to ctypes.Structure --- Doc/library/ctypes.rst | 10 +++ .../pycore_global_objects_fini_generated.h | 1 + Include/internal/pycore_global_strings.h | 1 + .../internal/pycore_runtime_init_generated.h | 1 + .../internal/pycore_unicodeobject_generated.h | 3 + .../test_ctypes/test_aligned_structures.py | 78 +++++++++++++++++++ Modules/_ctypes/stgdict.c | 27 ++++++- 7 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 Lib/test/test_ctypes/test_aligned_structures.py diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index ef3a9a0f5898af..0685985a319637 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -670,6 +670,10 @@ compiler does it. It is possible to override this behavior by specifying a :attr:`~Structure._pack_` class attribute in the subclass definition. This must be set to a positive integer and specifies the maximum alignment for the fields. This is what ``#pragma pack(n)`` also does in MSVC. +It is also possible to set a minimum alignment for how the subclass itself is packed in the +same way ``#pragma align(n)`` works in MSVC. +This can be achieved by specifying a ::attr:`~Structure._align_` class attribute +in the subclass definition. :mod:`ctypes` uses the native byte order for Structures and Unions. To build structures with non-native byte order, you can use one of the @@ -2534,6 +2538,12 @@ fields, or any other data types containing pointer type fields. Setting this attribute to 0 is the same as not setting it at all. + .. attribute:: _align_ + + An optional small interger that allows overriding the alignment of + the structure when being packed or unpacked to/from memory. + Setting this attribute to 0 is the same as not setting it at all. + .. attribute:: _anonymous_ An optional sequence that lists the names of unnamed (anonymous) fields. diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 89ec8cbbbcd649..995e6bcc87bf60 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -742,6 +742,7 @@ _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(_annotation)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_anonymous_)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_argtypes_)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 62c3ee3ae2a0bd..4d0cecb12bea22 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -231,6 +231,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(_abc_impl) STRUCT_FOR_ID(_abstract_) STRUCT_FOR_ID(_active) + STRUCT_FOR_ID(_align_) STRUCT_FOR_ID(_annotation) STRUCT_FOR_ID(_anonymous_) STRUCT_FOR_ID(_argtypes_) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 1defa39f816e78..90b4c5661d93f0 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -740,6 +740,7 @@ extern "C" { INIT_ID(_abc_impl), \ INIT_ID(_abstract_), \ INIT_ID(_active), \ + INIT_ID(_align_), \ INIT_ID(_annotation), \ INIT_ID(_anonymous_), \ INIT_ID(_argtypes_), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index be9baa3eebecfc..e61ed4c687f2c6 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -534,6 +534,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { string = &_Py_ID(_active); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); + string = &_Py_ID(_align_); + assert(_PyUnicode_CheckConsistency(string, 1)); + _PyUnicode_InternInPlace(interp, &string); string = &_Py_ID(_annotation); assert(_PyUnicode_CheckConsistency(string, 1)); _PyUnicode_InternInPlace(interp, &string); diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py new file mode 100644 index 00000000000000..c5034ced061415 --- /dev/null +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -0,0 +1,78 @@ +from ctypes import Structure, c_char, c_uint32, c_ubyte, alignment +import inspect +import unittest + + +class TestAlignedStructures(unittest.TestCase): + def test_aligned_string(self): + data = bytearray( + b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' + ) + + class Aligned(Structure): + _align_ = 16 + _fields_ = [ + ('value', c_char * 16), + ] + + class Main(Structure): + _fields_ = [ + ('first', c_uint32), + ('string', Aligned), + ] + + d = Main.from_buffer(data) + self.assertEqual(d.first, 7) + self.assertEqual(d.string.value, b'hello world!') + self.assertEqual( + bytes(d.string.__buffer__(inspect.BufferFlags.SIMPLE)), + b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' + ) + + def test_aligned_structures(self): + data = bytearray( + b'\x01\x00\x01\x00\x07\x00\x00\x00' + ) + + class SomeBools(Structure): + _align_ = 4 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class Main(Structure): + _fields_ = [ + ("x", SomeBools), + ("y", c_uint32), + ] + + class SomeBoolsTooBig(Structure): + _align_ = 8 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class MainTooBig(Structure): + _fields_ = [ + ("x", SomeBoolsTooBig), + ("y", c_uint32), + ] + d = Main.from_buffer(data) + self.assertEqual(d.y, 7) + self.assertEqual(alignment(SomeBools), 4) + self.assertEqual(d.x.bool1, True) + self.assertEqual(d.x.bool2, False) + self.assertEqual(d.x.bool3, True) + + with self.assertRaises(ValueError) as ctx: + MainTooBig.from_buffer(data) + self.assertEqual( + ctx.exception.args[0], + 'Buffer size too small (4 instead of at least 8 bytes)' + ) + +if __name__ == '__main__': + unittest.main() diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index fb3e20e8db3e27..ffd0c41043898e 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -379,6 +379,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct int bitofs; PyObject *tmp; int pack; + int forced_alignment = 1; Py_ssize_t ffi_ofs; int big_endian; int arrays_seen = 0; @@ -419,6 +420,28 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct pack = 0; } + 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; + } + len = PySequence_Size(fields); if (len == -1) { if (PyErr_ExceptionMatches(PyExc_TypeError)) { @@ -463,7 +486,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct size = offset = basedict->size; align = basedict->align; union_size = 0; - total_align = align ? align : 1; + total_align = align ? align : forced_alignment; stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT; stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, basedict->length + len + 1); if (stgdict->ffi_type_pointer.elements == NULL) { @@ -483,7 +506,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct size = 0; align = 0; union_size = 0; - total_align = 1; + total_align = forced_alignment; stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT; stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, len + 1); if (stgdict->ffi_type_pointer.elements == NULL) { From e2cceec0de491ecf81a9d579b25645033d36c384 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 02:46:13 +0000 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst new file mode 100644 index 00000000000000..00a0041c6f799c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst @@ -0,0 +1 @@ +Add ability to force alignment of :mod:`ctypes.Structure` by way of the new `_align_` attribute on the class. From 2329fa37328a5846b614a62b61fa9c31070ad5e9 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Sun, 28 Jan 2024 22:40:02 +1100 Subject: [PATCH 3/8] Fix issue with aligned subclasses --- .../test_ctypes/test_aligned_structures.py | 107 ++++++++++++++++-- ...-01-28-02-46-12.gh-issue-112433.FUX-nT.rst | 2 +- Modules/_ctypes/stgdict.c | 2 +- 3 files changed, 99 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py index c5034ced061415..9d421279202e39 100644 --- a/Lib/test/test_ctypes/test_aligned_structures.py +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -1,4 +1,4 @@ -from ctypes import Structure, c_char, c_uint32, c_ubyte, alignment +from ctypes import Structure, c_char, c_uint32, c_ubyte, alignment, sizeof, Union import inspect import unittest @@ -22,11 +22,11 @@ class Main(Structure): ('string', Aligned), ] - d = Main.from_buffer(data) - self.assertEqual(d.first, 7) - self.assertEqual(d.string.value, b'hello world!') + main = Main.from_buffer(data) + self.assertEqual(main.first, 7) + self.assertEqual(main.string.value, b'hello world!') self.assertEqual( - bytes(d.string.__buffer__(inspect.BufferFlags.SIMPLE)), + bytes(main.string.__buffer__(inspect.BufferFlags.SIMPLE)), b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' ) @@ -60,12 +60,12 @@ class MainTooBig(Structure): ("x", SomeBoolsTooBig), ("y", c_uint32), ] - d = Main.from_buffer(data) - self.assertEqual(d.y, 7) + main = Main.from_buffer(data) + self.assertEqual(main.y, 7) self.assertEqual(alignment(SomeBools), 4) - self.assertEqual(d.x.bool1, True) - self.assertEqual(d.x.bool2, False) - self.assertEqual(d.x.bool3, True) + self.assertEqual(main.x.bool1, True) + self.assertEqual(main.x.bool2, False) + self.assertEqual(main.x.bool3, True) with self.assertRaises(ValueError) as ctx: MainTooBig.from_buffer(data) @@ -74,5 +74,92 @@ class MainTooBig(Structure): 'Buffer size too small (4 instead of at least 8 bytes)' ) + def test_aligned_subclasses(self): + data = bytearray( + b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00" + ) + + class UnalignedSub(Structure): + x: c_uint32 + _fields_ = [ + ("x", c_uint32), + ] + + class AlignedStruct(UnalignedSub): + _align_ = 8 + y: c_uint32 + _fields_ = [ + ("y", c_uint32), + ] + + class Main(Structure): + _fields_ = [ + ("a", c_uint32), + ("b", AlignedStruct) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main.b), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(sizeof(main), 16) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 3) + self.assertEqual(main.b.y, 4) + + def test_aligned_union(self): + data = bytearray( + b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00" + ) + + class AlignedUnion(Union): + _align_ = 8 + _fields_ = [ + ("a", c_uint32), + ("b", c_ubyte * 8), + ] + + class Main(Structure): + _fields_ = [ + ("first", c_uint32), + ("union", AlignedUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(bytes(main.union.b), b"\x03\x00\x00\x00\x04\x00\x00\x00") + + def test_aligned_struct_in_union(self): + data = bytearray( + b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00" + ) + + class Sub(Structure): + _align_ = 8 + _fields_ = [ + ("x", c_uint32), + ("y", c_uint32), + ] + + class MainUnion(Union): + _fields_ = [ + ("a", c_uint32), + ("b", Sub), + ] + + class Main(Structure): + _fields_ = [ + ("first", c_uint32), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(main.union.b.x, 3) + self.assertEqual(main.union.b.y, 4) + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst index 00a0041c6f799c..fdd11bdf4241b9 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2024-01-28-02-46-12.gh-issue-112433.FUX-nT.rst @@ -1 +1 @@ -Add ability to force alignment of :mod:`ctypes.Structure` by way of the new `_align_` attribute on the class. +Add ability to force alignment of :mod:`ctypes.Structure` by way of the new ``_align_`` attribute on the class. diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index ffd0c41043898e..17aa7736406520 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -486,7 +486,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct size = offset = basedict->size; align = basedict->align; union_size = 0; - total_align = align ? align : forced_alignment; + total_align = max(align ? align : 1, forced_alignment); stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT; stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, basedict->length + len + 1); if (stgdict->ffi_type_pointer.elements == NULL) { From ebaed7ebba6dc79879537f90db8dea44445e4821 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Mon, 29 Jan 2024 10:03:13 +1100 Subject: [PATCH 4/8] Improve tests to pass on big endian machines --- .../test_ctypes/test_aligned_structures.py | 238 ++++++++++-------- Modules/_ctypes/stgdict.c | 3 +- 2 files changed, 129 insertions(+), 112 deletions(-) diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py index 9d421279202e39..507506005b7f63 100644 --- a/Lib/test/test_ctypes/test_aligned_structures.py +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -1,116 +1,134 @@ -from ctypes import Structure, c_char, c_uint32, c_ubyte, alignment, sizeof, Union +from ctypes import ( + c_char, c_uint32, c_ubyte, alignment, sizeof, + Structure, BigEndianStructure, LittleEndianStructure, + Union, BigEndianUnion, LittleEndianUnion, +) import inspect import unittest + class TestAlignedStructures(unittest.TestCase): def test_aligned_string(self): - data = bytearray( - b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' - ) - - class Aligned(Structure): - _align_ = 16 - _fields_ = [ - ('value', c_char * 16), - ] - - class Main(Structure): - _fields_ = [ - ('first', c_uint32), - ('string', Aligned), - ] - - main = Main.from_buffer(data) - self.assertEqual(main.first, 7) - self.assertEqual(main.string.value, b'hello world!') - self.assertEqual( - bytes(main.string.__buffer__(inspect.BufferFlags.SIMPLE)), - b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' - ) - - def test_aligned_structures(self): - data = bytearray( - b'\x01\x00\x01\x00\x07\x00\x00\x00' - ) - - class SomeBools(Structure): - _align_ = 4 - _fields_ = [ - ("bool1", c_ubyte), - ("bool2", c_ubyte), - ("bool3", c_ubyte), - ] - class Main(Structure): - _fields_ = [ - ("x", SomeBools), - ("y", c_uint32), - ] - - class SomeBoolsTooBig(Structure): - _align_ = 8 - _fields_ = [ - ("bool1", c_ubyte), - ("bool2", c_ubyte), - ("bool3", c_ubyte), - ] - class MainTooBig(Structure): - _fields_ = [ - ("x", SomeBoolsTooBig), - ("y", c_uint32), - ] - main = Main.from_buffer(data) - self.assertEqual(main.y, 7) - self.assertEqual(alignment(SomeBools), 4) - self.assertEqual(main.x.bool1, True) - self.assertEqual(main.x.bool2, False) - self.assertEqual(main.x.bool3, True) - - with self.assertRaises(ValueError) as ctx: - MainTooBig.from_buffer(data) + for base, data in ( + (LittleEndianStructure, bytearray( + b'\x07\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f' + b'\x72\x6c\x64\x21\x00\x00\x00\x00' + )), + (BigEndianStructure, bytearray( + b'\x00\x00\x00\x07\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f' + b'\x72\x6c\x64\x21\x00\x00\x00\x00' + )), + ): + + class Aligned(base): + _align_ = 16 + _fields_ = [ + ('value', c_char * 16), + ] + + class Main(base): + _fields_ = [ + ('first', c_uint32), + ('string', Aligned), + ] + + main = Main.from_buffer(data) + self.assertEqual(main.first, 7) + self.assertEqual(main.string.value, b'hello world!') self.assertEqual( - ctx.exception.args[0], - 'Buffer size too small (4 instead of at least 8 bytes)' + bytes(main.string.__buffer__(inspect.BufferFlags.SIMPLE)), + b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' ) + self.assertEqual(Main.string.offset, 16) - def test_aligned_subclasses(self): - data = bytearray( - b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00" - ) - - class UnalignedSub(Structure): - x: c_uint32 - _fields_ = [ - ("x", c_uint32), - ] - - class AlignedStruct(UnalignedSub): - _align_ = 8 - y: c_uint32 - _fields_ = [ - ("y", c_uint32), - ] - - class Main(Structure): - _fields_ = [ - ("a", c_uint32), - ("b", AlignedStruct) - ] + def test_aligned_structures(self): + for base, data in ( + (LittleEndianStructure, bytearray(b"\1\0\1\0\7\0\0\0")), + (BigEndianStructure, bytearray(b"\1\0\1\0\0\0\0\7")), + ): + class SomeBools(base): + _align_ = 4 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class Main(base): + _fields_ = [ + ("x", SomeBools), + ("y", c_uint32), + ] + + class SomeBoolsTooBig(base): + _align_ = 8 + _fields_ = [ + ("bool1", c_ubyte), + ("bool2", c_ubyte), + ("bool3", c_ubyte), + ] + class MainTooBig(base): + _fields_ = [ + ("x", SomeBoolsTooBig), + ("y", c_uint32), + ] + main = Main.from_buffer(data) + self.assertEqual(main.y, 7) + self.assertEqual(alignment(SomeBools), 4) + self.assertEqual(main.x.bool1, True) + self.assertEqual(main.x.bool2, False) + self.assertEqual(main.x.bool3, True) + + with self.assertRaises(ValueError) as ctx: + MainTooBig.from_buffer(data) + self.assertEqual( + ctx.exception.args[0], + 'Buffer size too small (4 instead of at least 8 bytes)' + ) - main = Main.from_buffer(data) - self.assertEqual(alignment(main.b), 8) - self.assertEqual(alignment(main), 8) - self.assertEqual(sizeof(main.b), 8) - self.assertEqual(sizeof(main), 16) - self.assertEqual(main.a, 1) - self.assertEqual(main.b.x, 3) - self.assertEqual(main.b.y, 4) + def test_aligned_subclasses(self): + for base, data in ( + (LittleEndianStructure, bytearray( + b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0" + )), + (BigEndianStructure, bytearray( + b"\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4" + )), + ): + class UnalignedSub(base): + x: c_uint32 + _fields_ = [ + ("x", c_uint32), + ] + + class AlignedStruct(UnalignedSub): + _align_ = 8 + y: c_uint32 + _fields_ = [ + ("y", c_uint32), + ] + + class Main(base): + _fields_ = [ + ("a", c_uint32), + ("b", AlignedStruct) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main.b), 8) + self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(sizeof(main), 16) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 3) + self.assertEqual(main.b.y, 4) def test_aligned_union(self): - data = bytearray( - b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00" - ) + data = bytearray(b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0") class AlignedUnion(Union): _align_ = 8 @@ -126,14 +144,11 @@ class Main(Structure): ] main = Main.from_buffer(data) - self.assertEqual(main.first, 1) - self.assertEqual(main.union.a, 3) - self.assertEqual(bytes(main.union.b), b"\x03\x00\x00\x00\x04\x00\x00\x00") + self.assertEqual(Main.union.offset, 8) + self.assertEqual(len(bytes(main.union.b)), 8) def test_aligned_struct_in_union(self): - data = bytearray( - b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00" - ) + data = bytearray(b"\1\0\0\0\2\0\0\0\3\0\0\3\4\0\0\4") class Sub(Structure): _align_ = 8 @@ -155,10 +170,11 @@ class Main(Structure): ] main = Main.from_buffer(data) - self.assertEqual(main.first, 1) - self.assertEqual(main.union.a, 3) - self.assertEqual(main.union.b.x, 3) - self.assertEqual(main.union.b.y, 4) + self.assertEqual(Main.first.size, 4) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(main.union.a, 0x03000003) + self.assertEqual(main.union.b.x, 0x03000003) + self.assertEqual(main.union.b.y, 0x04000004) if __name__ == '__main__': diff --git a/Modules/_ctypes/stgdict.c b/Modules/_ctypes/stgdict.c index 17aa7736406520..7d4f12a1214ca6 100644 --- a/Modules/_ctypes/stgdict.c +++ b/Modules/_ctypes/stgdict.c @@ -486,7 +486,8 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct size = offset = basedict->size; align = basedict->align; union_size = 0; - total_align = max(align ? align : 1, forced_alignment); + total_align = align ? align : 1; + total_align = max(total_align, forced_alignment); stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT; stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, basedict->length + len + 1); if (stgdict->ffi_type_pointer.elements == NULL) { From c8b37d4b34ebcc5c5b973224f2134e1903d5e9f6 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Mon, 29 Jan 2024 11:08:28 +1100 Subject: [PATCH 5/8] Further improvements to tests --- .../test_ctypes/test_aligned_structures.py | 111 ++++++++++-------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py index 507506005b7f63..acab66c0a6d6bd 100644 --- a/Lib/test/test_ctypes/test_aligned_structures.py +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -1,13 +1,12 @@ from ctypes import ( c_char, c_uint32, c_ubyte, alignment, sizeof, - Structure, BigEndianStructure, LittleEndianStructure, - Union, BigEndianUnion, LittleEndianUnion, + BigEndianStructure, LittleEndianStructure, + BigEndianUnion, LittleEndianUnion, ) import inspect import unittest - class TestAlignedStructures(unittest.TestCase): def test_aligned_string(self): for base, data in ( @@ -24,7 +23,6 @@ def test_aligned_string(self): b'\x72\x6c\x64\x21\x00\x00\x00\x00' )), ): - class Aligned(base): _align_ = 16 _fields_ = [ @@ -128,53 +126,72 @@ class Main(base): self.assertEqual(main.b.y, 4) def test_aligned_union(self): - data = bytearray(b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0") - - class AlignedUnion(Union): - _align_ = 8 - _fields_ = [ - ("a", c_uint32), - ("b", c_ubyte * 8), - ] + for sbase, ubase, data in ( + (LittleEndianStructure, LittleEndianUnion, bytearray( + b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0" + )), + (BigEndianStructure, BigEndianUnion, bytearray( + b"\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4" + )), + ): + class AlignedUnion(ubase): + _align_ = 8 + _fields_ = [ + ("a", c_uint32), + ("b", c_ubyte * 8), + ] - class Main(Structure): - _fields_ = [ - ("first", c_uint32), - ("union", AlignedUnion), - ] + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", AlignedUnion), + ] - main = Main.from_buffer(data) - self.assertEqual(Main.union.offset, 8) - self.assertEqual(len(bytes(main.union.b)), 8) + main = Main.from_buffer(data) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + if ubase == LittleEndianUnion: + self.assertEqual(bytes(main.union.b), b"\3\0\0\0\4\0\0\0") + else: + self.assertEqual(bytes(main.union.b), b"\0\0\0\3\0\0\0\4") + self.assertEqual(Main.union.offset, 8) + self.assertEqual(len(bytes(main.union.b)), 8) def test_aligned_struct_in_union(self): - data = bytearray(b"\1\0\0\0\2\0\0\0\3\0\0\3\4\0\0\4") - - class Sub(Structure): - _align_ = 8 - _fields_ = [ - ("x", c_uint32), - ("y", c_uint32), - ] - - class MainUnion(Union): - _fields_ = [ - ("a", c_uint32), - ("b", Sub), - ] - - class Main(Structure): - _fields_ = [ - ("first", c_uint32), - ("union", MainUnion), - ] - - main = Main.from_buffer(data) - self.assertEqual(Main.first.size, 4) - self.assertEqual(Main.union.offset, 8) - self.assertEqual(main.union.a, 0x03000003) - self.assertEqual(main.union.b.x, 0x03000003) - self.assertEqual(main.union.b.y, 0x04000004) + for sbase, ubase, data in ( + (LittleEndianStructure, LittleEndianUnion, bytearray( + b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0" + )), + (BigEndianStructure, BigEndianUnion, bytearray( + b"\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4" + )), + ): + class Sub(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint32), + ("y", c_uint32), + ] + + class MainUnion(ubase): + _fields_ = [ + ("a", c_uint32), + ("b", Sub), + ] + + class Main(sbase): + _fields_ = [ + ("first", c_uint32), + ("union", MainUnion), + ] + + main = Main.from_buffer(data) + self.assertEqual(Main.first.size, 4) + self.assertEqual(Main.union.offset, 8) + self.assertEqual(main.first, 1) + self.assertEqual(main.union.a, 3) + self.assertEqual(main.union.b.x, 3) + self.assertEqual(main.union.b.y, 4) if __name__ == '__main__': From 68e2d612fcf58e2abf59cf8acd7ef573e78cb7f2 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Tue, 30 Jan 2024 10:21:49 +1100 Subject: [PATCH 6/8] Test cleanup and improvements --- .../test_ctypes/test_aligned_structures.py | 167 +++++++++++------- 1 file changed, 105 insertions(+), 62 deletions(-) diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py index acab66c0a6d6bd..4f7a47e7893380 100644 --- a/Lib/test/test_ctypes/test_aligned_structures.py +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -1,28 +1,19 @@ from ctypes import ( - c_char, c_uint32, c_ubyte, alignment, sizeof, + c_char, c_uint32, c_ubyte, c_byte, alignment, sizeof, BigEndianStructure, LittleEndianStructure, BigEndianUnion, LittleEndianUnion, ) -import inspect +import struct import unittest class TestAlignedStructures(unittest.TestCase): def test_aligned_string(self): - for base, data in ( - (LittleEndianStructure, bytearray( - b'\x07\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f' - b'\x72\x6c\x64\x21\x00\x00\x00\x00' - )), - (BigEndianStructure, bytearray( - b'\x00\x00\x00\x07\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f' - b'\x72\x6c\x64\x21\x00\x00\x00\x00' - )), + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), ): + data = bytearray(struct.pack(f"{e}i12x16s", 7, b"hello world!")) class Aligned(base): _align_ = 16 _fields_ = [ @@ -38,16 +29,16 @@ class Main(base): main = Main.from_buffer(data) self.assertEqual(main.first, 7) self.assertEqual(main.string.value, b'hello world!') - self.assertEqual( - bytes(main.string.__buffer__(inspect.BufferFlags.SIMPLE)), - b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00' - ) + self.assertEqual(bytes(main.string), b'hello world!\0\0\0\0') self.assertEqual(Main.string.offset, 16) + self.assertEqual(Main.string.size, 16) + self.assertEqual(alignment(main.string), 16) + self.assertEqual(alignment(main), 16) def test_aligned_structures(self): for base, data in ( - (LittleEndianStructure, bytearray(b"\1\0\1\0\7\0\0\0")), - (BigEndianStructure, bytearray(b"\1\0\1\0\0\0\0\7")), + (LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\1\0\7\0\0\0")), + (BigEndianStructure, bytearray(b"\1\0\0\0\1\0\1\0\0\0\0\7")), ): class SomeBools(base): _align_ = 4 @@ -58,10 +49,27 @@ class SomeBools(base): ] class Main(base): _fields_ = [ - ("x", SomeBools), - ("y", c_uint32), + ("x", c_ubyte), + ("y", SomeBools), + ("z", c_uint32), ] + main = Main.from_buffer(data) + self.assertEqual(alignment(SomeBools), 4) + self.assertEqual(alignment(main), 4) + self.assertEqual(alignment(main.y), 4) + self.assertEqual(Main.x.size, 1) + self.assertEqual(Main.y.offset, 4) + self.assertEqual(Main.y.size, 4) + self.assertEqual(main.y.bool1, True) + self.assertEqual(main.y.bool2, False) + self.assertEqual(main.y.bool3, True) + self.assertEqual(Main.z.offset, 8) + self.assertEqual(main.z, 7) + + def test_oversized_structure(self): + data = bytearray(b"\0" * 8) + for base in (LittleEndianStructure, BigEndianStructure): class SomeBoolsTooBig(base): _align_ = 8 _fields_ = [ @@ -69,34 +77,24 @@ class SomeBoolsTooBig(base): ("bool2", c_ubyte), ("bool3", c_ubyte), ] - class MainTooBig(base): + class Main(base): _fields_ = [ - ("x", SomeBoolsTooBig), - ("y", c_uint32), + ("y", SomeBoolsTooBig), + ("z", c_uint32), ] - main = Main.from_buffer(data) - self.assertEqual(main.y, 7) - self.assertEqual(alignment(SomeBools), 4) - self.assertEqual(main.x.bool1, True) - self.assertEqual(main.x.bool2, False) - self.assertEqual(main.x.bool3, True) - with self.assertRaises(ValueError) as ctx: - MainTooBig.from_buffer(data) + Main.from_buffer(data) self.assertEqual( ctx.exception.args[0], 'Buffer size too small (4 instead of at least 8 bytes)' ) def test_aligned_subclasses(self): - for base, data in ( - (LittleEndianStructure, bytearray( - b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0" - )), - (BigEndianStructure, bytearray( - b"\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4" - )), + for base, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) class UnalignedSub(base): x: c_uint32 _fields_ = [ @@ -105,7 +103,6 @@ class UnalignedSub(base): class AlignedStruct(UnalignedSub): _align_ = 8 - y: c_uint32 _fields_ = [ ("y", c_uint32), ] @@ -124,21 +121,20 @@ class Main(base): self.assertEqual(main.a, 1) self.assertEqual(main.b.x, 3) self.assertEqual(main.b.y, 4) + self.assertEqual(Main.b.offset, 8) + self.assertEqual(Main.b.size, 8) def test_aligned_union(self): - for sbase, ubase, data in ( - (LittleEndianStructure, LittleEndianUnion, bytearray( - b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0" - )), - (BigEndianStructure, BigEndianUnion, bytearray( - b"\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4" - )), + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) class AlignedUnion(ubase): _align_ = 8 _fields_ = [ ("a", c_uint32), - ("b", c_ubyte * 8), + ("b", c_ubyte * 7), ] class Main(sbase): @@ -150,22 +146,18 @@ class Main(sbase): main = Main.from_buffer(data) self.assertEqual(main.first, 1) self.assertEqual(main.union.a, 3) - if ubase == LittleEndianUnion: - self.assertEqual(bytes(main.union.b), b"\3\0\0\0\4\0\0\0") - else: - self.assertEqual(bytes(main.union.b), b"\0\0\0\3\0\0\0\4") + self.assertEqual(bytes(main.union.b), data[8:-1]) self.assertEqual(Main.union.offset, 8) - self.assertEqual(len(bytes(main.union.b)), 8) + self.assertEqual(Main.union.size, 8) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) def test_aligned_struct_in_union(self): - for sbase, ubase, data in ( - (LittleEndianStructure, LittleEndianUnion, bytearray( - b"\1\0\0\0\2\0\0\0\3\0\0\0\4\0\0\0" - )), - (BigEndianStructure, BigEndianUnion, bytearray( - b"\0\0\0\1\0\0\0\2\0\0\0\3\0\0\0\4" - )), + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), ): + data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4)) class Sub(sbase): _align_ = 8 _fields_ = [ @@ -187,12 +179,63 @@ class Main(sbase): main = Main.from_buffer(data) self.assertEqual(Main.first.size, 4) + self.assertEqual(alignment(main.union), 8) + self.assertEqual(alignment(main), 8) self.assertEqual(Main.union.offset, 8) + self.assertEqual(Main.union.size, 8) self.assertEqual(main.first, 1) self.assertEqual(main.union.a, 3) self.assertEqual(main.union.b.x, 3) self.assertEqual(main.union.b.y, 4) + def test_smaller_aligned_subclassed_union(self): + for ubase, e in ( + (LittleEndianUnion, "<"), + (BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}I", 0xD60102D6)) + class SubUnion(ubase): + _align_ = 2 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class Main(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main), 4) + self.assertEqual(main.num, 0xD60102D6) + self.assertEqual(main.unsigned, 0xD6) + self.assertEqual(main.signed, -42) + + def test_larger_aligned_subclassed_union(self): + for ubase, e in ( + (LittleEndianUnion, "<"), + (BigEndianUnion, ">"), + ): + data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6)) + class SubUnion(ubase): + _align_ = 8 + _fields_ = [ + ("unsigned", c_ubyte), + ("signed", c_byte), + ] + + class Main(SubUnion): + _fields_ = [ + ("num", c_uint32) + ] + + main = Main.from_buffer(data) + self.assertEqual(alignment(main), 8) + self.assertEqual(main.num, 0xD60102D6) + self.assertEqual(main.unsigned, 0xD6) + self.assertEqual(main.signed, -42) + if __name__ == '__main__': unittest.main() From f6a93ddf38347f60b4c118af14e175bc532d6527 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Wed, 31 Jan 2024 11:24:00 +1100 Subject: [PATCH 7/8] More test improvements --- .../test_ctypes/test_aligned_structures.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py index 4f7a47e7893380..94ea8c5a47c4a4 100644 --- a/Lib/test/test_ctypes/test_aligned_structures.py +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -1,5 +1,5 @@ from ctypes import ( - c_char, c_uint32, c_ubyte, c_byte, alignment, sizeof, + c_char, c_uint32, c_uint16, c_ubyte, c_byte, alignment, sizeof, BigEndianStructure, LittleEndianStructure, BigEndianUnion, LittleEndianUnion, ) @@ -17,7 +17,7 @@ def test_aligned_string(self): class Aligned(base): _align_ = 16 _fields_ = [ - ('value', c_char * 16), + ('value', c_char * 12) ] class Main(base): @@ -37,21 +37,20 @@ class Main(base): def test_aligned_structures(self): for base, data in ( - (LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\1\0\7\0\0\0")), - (BigEndianStructure, bytearray(b"\1\0\0\0\1\0\1\0\0\0\0\7")), + (LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), + (BigEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")), ): class SomeBools(base): _align_ = 4 _fields_ = [ ("bool1", c_ubyte), ("bool2", c_ubyte), - ("bool3", c_ubyte), ] class Main(base): _fields_ = [ ("x", c_ubyte), ("y", SomeBools), - ("z", c_uint32), + ("z", c_ubyte), ] main = Main.from_buffer(data) @@ -63,7 +62,6 @@ class Main(base): self.assertEqual(Main.y.size, 4) self.assertEqual(main.y.bool1, True) self.assertEqual(main.y.bool2, False) - self.assertEqual(main.y.bool3, True) self.assertEqual(Main.z.offset, 8) self.assertEqual(main.z, 7) @@ -189,11 +187,11 @@ class Main(sbase): self.assertEqual(main.union.b.y, 4) def test_smaller_aligned_subclassed_union(self): - for ubase, e in ( - (LittleEndianUnion, "<"), - (BigEndianUnion, ">"), + for sbase, ubase, e in ( + (LittleEndianStructure, LittleEndianUnion, "<"), + (BigEndianStructure, BigEndianUnion, ">"), ): - data = bytearray(struct.pack(f"{e}I", 0xD60102D6)) + data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7)) class SubUnion(ubase): _align_ = 2 _fields_ = [ @@ -201,16 +199,26 @@ class SubUnion(ubase): ("signed", c_byte), ] - class Main(SubUnion): + class MainUnion(SubUnion): _fields_ = [ ("num", c_uint32) ] + class Main(sbase): + _fields_ = [ + ("first", c_uint16), + ("union", MainUnion), + ] + main = Main.from_buffer(data) + self.assertEqual(main.union.num, 0xD60102D7) + self.assertEqual(main.union.unsigned, data[4]) + self.assertEqual(main.union.signed, data[4] - 256) self.assertEqual(alignment(main), 4) - self.assertEqual(main.num, 0xD60102D6) - self.assertEqual(main.unsigned, 0xD6) - self.assertEqual(main.signed, -42) + self.assertEqual(alignment(main.union), 4) + self.assertEqual(Main.union.offset, 4) + self.assertEqual(Main.union.size, 4) + self.assertEqual(Main.first.size, 2) def test_larger_aligned_subclassed_union(self): for ubase, e in ( @@ -232,6 +240,7 @@ class Main(SubUnion): main = Main.from_buffer(data) self.assertEqual(alignment(main), 8) + self.assertEqual(sizeof(main), 8) self.assertEqual(main.num, 0xD60102D6) self.assertEqual(main.unsigned, 0xD6) self.assertEqual(main.signed, -42) From a7bc0fef39ea871bd598ffb1fadd10fd7dc17ca7 Mon Sep 17 00:00:00 2001 From: monkeyman192 Date: Fri, 2 Feb 2024 16:17:17 +1100 Subject: [PATCH 8/8] Add extra test and fix documentation typo --- Doc/library/ctypes.rst | 2 +- .../test_ctypes/test_aligned_structures.py | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 0685985a319637..73779547b35a1f 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -2540,7 +2540,7 @@ fields, or any other data types containing pointer type fields. .. attribute:: _align_ - An optional small interger that allows overriding the alignment of + An optional small integer that allows overriding the alignment of the structure when being packed or unpacked to/from memory. Setting this attribute to 0 is the same as not setting it at all. diff --git a/Lib/test/test_ctypes/test_aligned_structures.py b/Lib/test/test_ctypes/test_aligned_structures.py index 94ea8c5a47c4a4..a208fb9a00966a 100644 --- a/Lib/test/test_ctypes/test_aligned_structures.py +++ b/Lib/test/test_ctypes/test_aligned_structures.py @@ -245,6 +245,42 @@ class Main(SubUnion): self.assertEqual(main.unsigned, 0xD6) self.assertEqual(main.signed, -42) + def test_aligned_packed_structures(self): + for sbase, e in ( + (LittleEndianStructure, "<"), + (BigEndianStructure, ">"), + ): + data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4)) + + class Inner(sbase): + _align_ = 8 + _fields_ = [ + ("x", c_uint16), + ("y", c_uint16), + ] + + class Main(sbase): + _pack_ = 1 + _fields_ = [ + ("a", c_ubyte), + ("b", Inner), + ("c", c_ubyte), + ] + + main = Main.from_buffer(data) + self.assertEqual(sizeof(main), 10) + self.assertEqual(Main.b.offset, 1) + # Alignment == 8 because _pack_ wins out. + self.assertEqual(alignment(main.b), 8) + # Size is still 8 though since inside this Structure, it will have + # effect. + self.assertEqual(sizeof(main.b), 8) + self.assertEqual(Main.c.offset, 9) + self.assertEqual(main.a, 1) + self.assertEqual(main.b.x, 2) + self.assertEqual(main.b.y, 3) + self.assertEqual(main.c, 4) + if __name__ == '__main__': unittest.main()