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() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy