From dc2d8404a3ab6288ce112c71da8c65c34cd3087e Mon Sep 17 00:00:00 2001 From: Dave Goncalves Date: Tue, 29 Mar 2022 14:26:27 -0700 Subject: bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes (GH-25480) * bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes * GH-25480: remove trailing whitespace in ctypes doc * GH-25480: add news entry blurb * GH-25480: corrected formatting error in news blurb * GH-25480: simplified, corrected formatting in news blurb * GH-25480: remove trailing whitespace in news blurb * GH-25480: fixed class markup in news blurb * GH-25480: fixed unsupported type tests and naming per review comments * GH-25480: fixed whitepace errors * condensed base class selection for unsupported byte order tests * added versionadded tags for new EndianUnion classes --- Doc/library/ctypes.rst | 16 ++- Lib/ctypes/__init__.py | 1 + Lib/ctypes/_endian.py | 23 ++++- Lib/ctypes/test/test_byteswap.py | 115 +++++++++++++++------ .../2021-04-20-16-48-07.bpo-33178.kSnWwb.rst | 1 + 5 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-04-20-16-48-07.bpo-33178.kSnWwb.rst diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index dca4c74..6cca569 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -2390,6 +2390,18 @@ Structured data types Abstract base class for unions in native byte order. +.. class:: BigEndianUnion(*args, **kw) + + Abstract base class for unions in *big endian* byte order. + + .. versionadded:: 3.11 + +.. class:: LittleEndianUnion(*args, **kw) + + Abstract base class for unions in *little endian* byte order. + + .. versionadded:: 3.11 + .. class:: BigEndianStructure(*args, **kw) Abstract base class for structures in *big endian* byte order. @@ -2399,8 +2411,8 @@ Structured data types Abstract base class for structures in *little endian* byte order. -Structures with non-native byte order cannot contain pointer type fields, or any -other data types containing pointer type fields. +Structures and unions with non-native byte order cannot contain pointer type +fields, or any other data types containing pointer type fields. .. class:: Structure(*args, **kw) diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index ab4d31b..26135ad 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -548,6 +548,7 @@ if _os.name == "nt": # COM stuff return ccom.DllCanUnloadNow() from ctypes._endian import BigEndianStructure, LittleEndianStructure +from ctypes._endian import BigEndianUnion, LittleEndianUnion # Fill in specifically-sized types c_int8 = c_byte diff --git a/Lib/ctypes/_endian.py b/Lib/ctypes/_endian.py index 37444bd..34dee64 100644 --- a/Lib/ctypes/_endian.py +++ b/Lib/ctypes/_endian.py @@ -20,7 +20,7 @@ def _other_endian(typ): return typ raise TypeError("This type does not support other endian: %s" % typ) -class _swapped_meta(type(Structure)): +class _swapped_meta: def __setattr__(self, attrname, value): if attrname == "_fields_": fields = [] @@ -31,6 +31,8 @@ class _swapped_meta(type(Structure)): fields.append((name, _other_endian(typ)) + rest) value = fields super().__setattr__(attrname, value) +class _swapped_struct_meta(_swapped_meta, type(Structure)): pass +class _swapped_union_meta(_swapped_meta, type(Union)): pass ################################################################ @@ -43,19 +45,34 @@ if sys.byteorder == "little": LittleEndianStructure = Structure - class BigEndianStructure(Structure, metaclass=_swapped_meta): + class BigEndianStructure(Structure, metaclass=_swapped_struct_meta): """Structure with big endian byte order""" __slots__ = () _swappedbytes_ = None + LittleEndianUnion = Union + + class BigEndianUnion(Union, metaclass=_swapped_union_meta): + """Union with big endian byte order""" + __slots__ = () + _swappedbytes_ = None + elif sys.byteorder == "big": _OTHER_ENDIAN = "__ctype_le__" BigEndianStructure = Structure - class LittleEndianStructure(Structure, metaclass=_swapped_meta): + + class LittleEndianStructure(Structure, metaclass=_swapped_struct_meta): """Structure with little endian byte order""" __slots__ = () _swappedbytes_ = None + BigEndianUnion = Union + + class LittleEndianUnion(Union, metaclass=_swapped_union_meta): + """Union with little endian byte order""" + __slots__ = () + _swappedbytes_ = None + else: raise RuntimeError("Invalid byteorder") diff --git a/Lib/ctypes/test/test_byteswap.py b/Lib/ctypes/test/test_byteswap.py index 01c97e8..7e98559 100644 --- a/Lib/ctypes/test/test_byteswap.py +++ b/Lib/ctypes/test/test_byteswap.py @@ -170,40 +170,34 @@ class Test(unittest.TestCase): self.assertIs(c_char.__ctype_le__, c_char) self.assertIs(c_char.__ctype_be__, c_char) - def test_struct_fields_1(self): - if sys.byteorder == "little": - base = BigEndianStructure - else: - base = LittleEndianStructure - - class T(base): - pass - _fields_ = [("a", c_ubyte), - ("b", c_byte), - ("c", c_short), - ("d", c_ushort), - ("e", c_int), - ("f", c_uint), - ("g", c_long), - ("h", c_ulong), - ("i", c_longlong), - ("k", c_ulonglong), - ("l", c_float), - ("m", c_double), - ("n", c_char), - - ("b1", c_byte, 3), - ("b2", c_byte, 3), - ("b3", c_byte, 2), - ("a", c_int * 3 * 3 * 3)] - T._fields_ = _fields_ + def test_struct_fields_unsupported_byte_order(self): + + fields = [ + ("a", c_ubyte), + ("b", c_byte), + ("c", c_short), + ("d", c_ushort), + ("e", c_int), + ("f", c_uint), + ("g", c_long), + ("h", c_ulong), + ("i", c_longlong), + ("k", c_ulonglong), + ("l", c_float), + ("m", c_double), + ("n", c_char), + ("b1", c_byte, 3), + ("b2", c_byte, 3), + ("b3", c_byte, 2), + ("a", c_int * 3 * 3 * 3) + ] # these fields do not support different byte order: for typ in c_wchar, c_void_p, POINTER(c_int): - _fields_.append(("x", typ)) - class T(base): - pass - self.assertRaises(TypeError, setattr, T, "_fields_", [("x", typ)]) + with self.assertRaises(TypeError): + class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure): + _fields_ = fields + [("x", typ)] + def test_struct_struct(self): # nested structures with different byteorders @@ -233,7 +227,7 @@ class Test(unittest.TestCase): self.assertEqual(s.point.x, 1) self.assertEqual(s.point.y, 2) - def test_struct_fields_2(self): + def test_struct_field_alignment(self): # standard packing in struct uses no alignment. # So, we have to align using pad bytes. # @@ -267,7 +261,6 @@ class Test(unittest.TestCase): class S(base): _pack_ = 1 _fields_ = [("b", c_byte), - ("h", c_short), ("_1", c_byte), @@ -311,5 +304,61 @@ class Test(unittest.TestCase): s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14) self.assertEqual(bin(s1), bin(s2)) + def test_union_fields_unsupported_byte_order(self): + + fields = [ + ("a", c_ubyte), + ("b", c_byte), + ("c", c_short), + ("d", c_ushort), + ("e", c_int), + ("f", c_uint), + ("g", c_long), + ("h", c_ulong), + ("i", c_longlong), + ("k", c_ulonglong), + ("l", c_float), + ("m", c_double), + ("n", c_char), + ("b1", c_byte, 3), + ("b2", c_byte, 3), + ("b3", c_byte, 2), + ("a", c_int * 3 * 3 * 3) + ] + + # these fields do not support different byte order: + for typ in c_wchar, c_void_p, POINTER(c_int): + with self.assertRaises(TypeError): + class T(BigEndianUnion if sys.byteorder == "little" else LittleEndianUnion): + _fields_ = fields + [("x", typ)] + + def test_union_struct(self): + # nested structures in unions with different byteorders + + # create nested structures in unions with given byteorders and set memory to data + + for nested, data in ( + (BigEndianStructure, b'\0\0\0\1\0\0\0\2'), + (LittleEndianStructure, b'\1\0\0\0\2\0\0\0'), + ): + for parent in ( + BigEndianUnion, + LittleEndianUnion, + Union, + ): + class NestedStructure(nested): + _fields_ = [("x", c_uint32), + ("y", c_uint32)] + + class TestUnion(parent): + _fields_ = [("point", NestedStructure)] + + self.assertEqual(len(data), sizeof(TestUnion)) + ptr = POINTER(TestUnion) + s = cast(data, ptr)[0] + del ctypes._pointer_type_cache[TestUnion] + self.assertEqual(s.point.x, 1) + self.assertEqual(s.point.y, 2) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2021-04-20-16-48-07.bpo-33178.kSnWwb.rst b/Misc/NEWS.d/next/Library/2021-04-20-16-48-07.bpo-33178.kSnWwb.rst new file mode 100644 index 0000000..3646e4a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-04-20-16-48-07.bpo-33178.kSnWwb.rst @@ -0,0 +1 @@ +Added :class:`ctypes.BigEndianUnion` and :class:`ctypes.LittleEndianUnion` classes, as originally documented in the library docs but not yet implemented. -- cgit v0.12