]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes (GH-25480)
authorDave Goncalves <davegoncalves@gmail.com>
Tue, 29 Mar 2022 21:26:27 +0000 (14:26 -0700)
committerGitHub <noreply@github.com>
Tue, 29 Mar 2022 21:26:27 +0000 (14:26 -0700)
* 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
Lib/ctypes/__init__.py
Lib/ctypes/_endian.py
Lib/ctypes/test/test_byteswap.py
Misc/NEWS.d/next/Library/2021-04-20-16-48-07.bpo-33178.kSnWwb.rst [new file with mode: 0644]

index dca4c74bab7714a32b66fde8cf9b0641cfcf4208..6cca569a3f1c843df94aae4b2ee188ea75d405c0 100644 (file)
@@ -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)
index ab4d31b0acb00286ea64fd133e9eacd9d0a18a14..26135ad96296acc6aeda25d8b686f780e0b9e2c4 100644 (file)
@@ -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
index 37444bd6a7dd60263d2833312643b02c899776ea..34dee64b1a65a693b34417c274d1de9ccdc05123 100644 (file)
@@ -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")
index 01c97e83ca7af68592232af930442cb5c6436ea5..7e98559dfbccb64add67837e3972b84ac7302bc9 100644 (file)
@@ -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 (file)
index 0000000..3646e4a
--- /dev/null
@@ -0,0 +1 @@
+Added :class:`ctypes.BigEndianUnion` and :class:`ctypes.LittleEndianUnion` classes, as originally documented in the library docs but not yet implemented.