Prechádzať zdrojové kódy

ctypesgen: remove unnamed zero bit sized structure members (#2079)

from generated files, as they are not supported by Ctypes.

Addresses issue reported in https://github.com/OSGeo/grass/pull/2073
Nicklas Larsson 3 rokov pred
rodič
commit
6aaffa6dcb

+ 158 - 0
python/libgrass_interface_generator/README.md

@@ -62,6 +62,164 @@ file generation.
 
 ```
 
+#### Ctypes "unnamed structure member with 0 bit size"-patch
+
+Using unnamed zero bit sized structure members, e.g.:
+
+```c
+struct timespec {
+    time_t tv_sec;
+    int :8*(sizeof(time_t)-sizeof(long))*(__BYTE_ORDER==4321);
+    long tv_nsec;
+    int :8*(sizeof(time_t)-sizeof(long))*(__BYTE_ORDER!=4321);
+};
+```
+
+is not supported by Ctypes. Ctypesgen generates this code to:
+
+```py
+struct_timespec._fields_ = [                         
+    ('tv_sec', time_t),                           
+    ('unnamed_1', c_int, ((8 * (sizeof(time_t) - sizeof(c_long))) * (1234 == 4321))),
+    ('tv_nsec', c_long),                                                             
+    ('unnamed_2', c_int, ((8 * (sizeof(time_t) - sizeof(c_long))) * (1234 != 4321))),
+]  
+```
+
+which therefore causes `ValueError: number of bits invalid for bit field`
+(if the bit size expression equals 0).
+
+Reported with: https://github.com/OSGeo/grass/pull/2073
+
+This patch removes the zero bit sized unnamed structure members from the
+generated files.
+
+```diff
+--- ctypesgen/printer_python/printer.py.orig
++++ ctypesgen/printer_python/printer.py
+@@ -46,6 +46,7 @@
+ 
+         self.file = open(outpath, "w") if outpath else sys.stdout
+         self.options = options
++        self.has_unnamed_struct_member = False
+ 
+         if self.options.strip_build_path and self.options.strip_build_path[-1] != os.path.sep:
+             self.options.strip_build_path += os.path.sep
+@@ -82,9 +83,14 @@
+         self.print_group(self.options.inserted_files, "inserted files", self.insert_file)
+         self.strip_prefixes()
+ 
+-    def __del__(self):
++        if self.has_unnamed_struct_member and outpath:
++            self._add_remove_zero_bitfields()
++
+         self.file.close()
+ 
++        if self.has_unnamed_struct_member and outpath and sys.executable:
++            os.system("{0} {1}".format(sys.executable, outpath))
++
+     def print_group(self, list, name, function):
+         if list:
+             self.file.write("# Begin %s\n" % name)
+@@ -231,6 +237,7 @@
+             mem = list(struct.members[mi])
+             if mem[0] is None:
+                 while True:
++                    self.has_unnamed_struct_member = True
+                     name = "%s%i" % (anon_prefix, n)
+                     n += 1
+                     if name not in names:
+@@ -243,7 +250,10 @@
+ 
+         self.file.write("%s_%s.__slots__ = [\n" % (struct.variety, struct.tag))
+         for name, ctype in struct.members:
+-            self.file.write("    '%s',\n" % name)
++            skip_unnamed = (
++                "#unnamedbitfield_{0} ".format(struct.tag) if name.startswith(anon_prefix) else ""
++            )
++            self.file.write("    {0}'{1}',\n".format(skip_unnamed, name))
+         self.file.write("]\n")
+ 
+         if len(unnamed_fields) > 0:
+@@ -255,9 +265,15 @@
+         self.file.write("%s_%s._fields_ = [\n" % (struct.variety, struct.tag))
+         for name, ctype in struct.members:
+             if isinstance(ctype, CtypesBitfield):
++                skip_unnamed = (
++                    "#unnamedbitfield_{0} ".format(struct.tag)
++                    if name.startswith(anon_prefix)
++                    else ""
++                )
+                 self.file.write(
+-                    "    ('%s', %s, %s),\n"
+-                    % (name, ctype.py_string(), ctype.bitfield.py_string(False))
++                    "    {0}('{1}', {2}, {3}),\n".format(
++                        skip_unnamed, name, ctype.py_string(), ctype.bitfield.py_string(False)
++                    )
+                 )
+             else:
+                 self.file.write("    ('%s', %s),\n" % (name, ctype.py_string()))
+@@ -458,3 +474,57 @@
+         )
+ 
+         inserted_file.close()
++
++    def _add_remove_zero_bitfields(self):
++        self.file.write(
++            "#REMOVE_START\n"
++            "def main():\n"
++            "    zero_bitfield_list = list()\n"
++            "    filename = os.path.abspath(__file__)\n"
++            "\n"
++            '    with open(filename, "r") as f:\n'
++            "        regex = re.compile(\n"
++            r'            r"([\s]*)(\#unnamedbitfield)_"'
++            "\n"
++            r'            r"(?P<struct_name>[a-zA-Z_].[a-zA-Z0-9_]*)\s(?P<expr>.*)\,"'
++            "\n"
++            "        )\n"
++            "        for line in f:\n"
++            "            m = regex.match(line)\n"
++            "            if m:\n"
++            '                struct_name = m.group("struct_name")\n'
++            '                bitfield_expression = tuple(eval(m.group("expr")))\n'
++            "\n"
++            "                if len(bitfield_expression) == 3 and bitfield_expression[2] == 0:\n"
++            "                    member = bitfield_expression[0]\n"
++            "                    zero_bitfield_list.append((struct_name, member))\n"
++            "\n"
++            '    with open(filename, "r+") as f:\n'
++            "        filedata = f.read()\n"
++            "\n"
++            "        for (struct_name, member) in zero_bitfield_list:\n"
++            "            pat = re.compile(\n"
++            r"""                r"( *)#unnamedbitfield_{0}( '| \('){1}.*\n".format("""
++            "\n"
++            "                    struct_name, member\n"
++            "                )\n"
++            "            )\n"
++            '            filedata = pat.sub("", filedata)\n'
++            "\n"
++            r'        regex = re.compile(r"#REMOVE_START.*#REMOVE_END\n", re.DOTALL)'
++            "\n"
++            '        filedata = regex.sub("", filedata)\n'
++            "\n"
++            r"""        regex = re.compile(r"#unnamedbitfield_[^'\(]*")"""
++            "\n"
++            '        filedata = regex.sub("", filedata)\n'
++            "\n"
++            "        f.seek(0)\n"
++            "        f.write(filedata)\n"
++            "        f.truncate()\n"
++            "\n"
++            "\n"
++            'if __name__ == "__main__":\n'
++            "    main()\n"
++            "#REMOVE_END\n"
++        )
+
+```
+
 #### Loader and preamble patch
 
 Replaces sed introduced with:

+ 74 - 4
python/libgrass_interface_generator/ctypesgen/printer_python/printer.py

@@ -46,6 +46,7 @@ class WrapperPrinter:
 
         self.file = open(outpath, "w") if outpath else sys.stdout
         self.options = options
+        self.has_unnamed_struct_member = False
 
         if self.options.strip_build_path and self.options.strip_build_path[-1] != os.path.sep:
             self.options.strip_build_path += os.path.sep
@@ -82,9 +83,14 @@ class WrapperPrinter:
         self.print_group(self.options.inserted_files, "inserted files", self.insert_file)
         self.strip_prefixes()
 
-    def __del__(self):
+        if self.has_unnamed_struct_member and outpath:
+            self._add_remove_zero_bitfields()
+
         self.file.close()
 
+        if self.has_unnamed_struct_member and outpath and sys.executable:
+            os.system("{0} {1}".format(sys.executable, outpath))
+
     def print_group(self, list, name, function):
         if list:
             self.file.write("# Begin %s\n" % name)
@@ -231,6 +237,7 @@ class WrapperPrinter:
             mem = list(struct.members[mi])
             if mem[0] is None:
                 while True:
+                    self.has_unnamed_struct_member = True
                     name = "%s%i" % (anon_prefix, n)
                     n += 1
                     if name not in names:
@@ -243,7 +250,10 @@ class WrapperPrinter:
 
         self.file.write("%s_%s.__slots__ = [\n" % (struct.variety, struct.tag))
         for name, ctype in struct.members:
-            self.file.write("    '%s',\n" % name)
+            skip_unnamed = (
+                "#unnamedbitfield_{0} ".format(struct.tag) if name.startswith(anon_prefix) else ""
+            )
+            self.file.write("    {0}'{1}',\n".format(skip_unnamed, name))
         self.file.write("]\n")
 
         if len(unnamed_fields) > 0:
@@ -255,9 +265,15 @@ class WrapperPrinter:
         self.file.write("%s_%s._fields_ = [\n" % (struct.variety, struct.tag))
         for name, ctype in struct.members:
             if isinstance(ctype, CtypesBitfield):
+                skip_unnamed = (
+                    "#unnamedbitfield_{0} ".format(struct.tag)
+                    if name.startswith(anon_prefix)
+                    else ""
+                )
                 self.file.write(
-                    "    ('%s', %s, %s),\n"
-                    % (name, ctype.py_string(), ctype.bitfield.py_string(False))
+                    "    {0}('{1}', {2}, {3}),\n".format(
+                        skip_unnamed, name, ctype.py_string(), ctype.bitfield.py_string(False)
+                    )
                 )
             else:
                 self.file.write("    ('%s', %s),\n" % (name, ctype.py_string()))
@@ -458,3 +474,57 @@ class WrapperPrinter:
         )
 
         inserted_file.close()
+
+    def _add_remove_zero_bitfields(self):
+        self.file.write(
+            "#REMOVE_START\n"
+            "def main():\n"
+            "    zero_bitfield_list = list()\n"
+            "    filename = os.path.abspath(__file__)\n"
+            "\n"
+            '    with open(filename, "r") as f:\n'
+            "        regex = re.compile(\n"
+            r'            r"([\s]*)(\#unnamedbitfield)_"'
+            "\n"
+            r'            r"(?P<struct_name>[a-zA-Z_].[a-zA-Z0-9_]*)\s(?P<expr>.*)\,"'
+            "\n"
+            "        )\n"
+            "        for line in f:\n"
+            "            m = regex.match(line)\n"
+            "            if m:\n"
+            '                struct_name = m.group("struct_name")\n'
+            '                bitfield_expression = tuple(eval(m.group("expr")))\n'
+            "\n"
+            "                if len(bitfield_expression) == 3 and bitfield_expression[2] == 0:\n"
+            "                    member = bitfield_expression[0]\n"
+            "                    zero_bitfield_list.append((struct_name, member))\n"
+            "\n"
+            '    with open(filename, "r+") as f:\n'
+            "        filedata = f.read()\n"
+            "\n"
+            "        for (struct_name, member) in zero_bitfield_list:\n"
+            "            pat = re.compile(\n"
+            r"""                r"( *)#unnamedbitfield_{0}( '| \('){1}.*\n".format("""
+            "\n"
+            "                    struct_name, member\n"
+            "                )\n"
+            "            )\n"
+            '            filedata = pat.sub("", filedata)\n'
+            "\n"
+            r'        regex = re.compile(r"#REMOVE_START.*#REMOVE_END\n", re.DOTALL)'
+            "\n"
+            '        filedata = regex.sub("", filedata)\n'
+            "\n"
+            r"""        regex = re.compile(r"#unnamedbitfield_[^'\(]*")"""
+            "\n"
+            '        filedata = regex.sub("", filedata)\n'
+            "\n"
+            "        f.seek(0)\n"
+            "        f.write(filedata)\n"
+            "        f.truncate()\n"
+            "\n"
+            "\n"
+            'if __name__ == "__main__":\n'
+            "    main()\n"
+            "#REMOVE_END\n"
+        )