• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

trolldbois / ctypeslib / 13391439098

18 Feb 2025 01:15PM UTC coverage: 83.553% (-2.0%) from 85.503%
13391439098

push

github

trolldbois
update some doc

2032 of 2432 relevant lines covered (83.55%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.51
/ctypeslib/codegen/clangparser.py
1
"""clangparser - use clang to get preprocess a source code."""
2

3
import logging
1✔
4
import os
1✔
5
import collections
1✔
6

7
from clang.cindex import Index, TranslationUnit
1✔
8
from clang.cindex import TypeKind
1✔
9

10
from ctypeslib.codegen import cursorhandler
1✔
11
from ctypeslib.codegen import typedesc
1✔
12
from ctypeslib.codegen import typehandler
1✔
13
from ctypeslib.codegen import util
1✔
14
from ctypeslib.codegen.handler import DuplicateDefinitionException
1✔
15
from ctypeslib.codegen.handler import InvalidDefinitionError
1✔
16
from ctypeslib.codegen.handler import InvalidTranslationUnitException
1✔
17

18
log = logging.getLogger("clangparser")
1✔
19

20

21
class Clang_Parser:
1✔
22
    """
23
    Will parse libclang AST tree to create a representation of Types and
24
    different others source code objects objets as described in Typedesc.
25

26
    For each Declaration a declaration will be saved, and the type of that
27
    declaration will be cached and saved.
28
    """
29

30
    has_values = {
1✔
31
        "Enumeration",
32
        "Function",
33
        "FunctionType",
34
        "OperatorFunction",
35
        "Method",
36
        "Constructor",
37
        "Destructor",
38
        "OperatorMethod",
39
        "Converter",
40
    }
41

42
    # FIXME, macro definition __SIZEOF_DOUBLE__
43
    ctypes_typename = {
1✔
44
        TypeKind.VOID: "None",  # because ctypes.POINTER(None) == c_void_p
45
        TypeKind.BOOL: "c_bool",
46
        TypeKind.CHAR_U: "c_ubyte",  # ?? used for PADDING
47
        TypeKind.UCHAR: "c_ubyte",  # unsigned char
48
        TypeKind.CHAR16: "c_wchar",  # char16_t
49
        TypeKind.CHAR32: "c_wchar",  # char32_t
50
        TypeKind.USHORT: "c_ushort",
51
        TypeKind.UINT: "c_uint",
52
        TypeKind.ULONG: "TBD",
53
        TypeKind.ULONGLONG: "c_ulonglong",
54
        TypeKind.UINT128: "c_uint128",  # FIXME
55
        TypeKind.CHAR_S: "c_char",  # char
56
        TypeKind.SCHAR: "c_byte",  # signed char
57
        TypeKind.WCHAR: "c_wchar",
58
        TypeKind.SHORT: "c_short",
59
        TypeKind.INT: "c_int",
60
        TypeKind.LONG: "TBD",
61
        TypeKind.LONGLONG: "c_longlong",
62
        TypeKind.INT128: "c_int128",  # FIXME
63
        TypeKind.FLOAT: "c_float",
64
        TypeKind.DOUBLE: "c_double",
65
        TypeKind.LONGDOUBLE: "c_longdouble",
66
        TypeKind.POINTER: "POINTER_T",
67
        TypeKind.NULLPTR: "c_void_p",
68
    }
69

70
    def __init__(self, flags):
1✔
71
        self.all = collections.OrderedDict()
1✔
72
        # a shortcut to identify registered decl in cases of records
73
        self.all_set = set()
1✔
74
        self.cpp_data = {}
1✔
75
        self._unhandled = []
1✔
76
        self.fields = {}
1✔
77
        self.tu = None
1✔
78
        self.tu_options = None
1✔
79
        self.flags = flags
1✔
80
        self.ctypes_sizes = {}
1✔
81
        self.init_parsing_options()
1✔
82
        self.make_ctypes_convertor(flags)
1✔
83
        self.cursorkind_handler = cursorhandler.CursorHandler(self)
1✔
84
        self.typekind_handler = typehandler.TypeHandler(self)
1✔
85
        self.__filter_location = None
1✔
86
        self.__processed_location = set()
1✔
87

88
    def init_parsing_options(self):
1✔
89
        """Set the Translation Unit to skip functions bodies per default."""
90
        self.tu_options = TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
1✔
91

92
    def activate_macros_parsing(self):
1✔
93
        """Activates the detailled code parsing options in the Translation
94
        Unit."""
95
        self.tu_options |= TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD
1✔
96

97
    def activate_comment_parsing(self):
1✔
98
        """Activates the comment parsing options in the Translation Unit."""
99
        self.tu_options |= TranslationUnit.PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION
1✔
100

101
    def deactivate_function_body_parsing(self):
1✔
102
        self.tu_options |= TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
×
103

104
    def filter_location(self, src_files):
1✔
105
        self.__filter_location = [os.path.abspath(f) for f in src_files]
1✔
106

107
    def parse(self, filename):
1✔
108
        """
109
        . reads 1 file
110
        . if there is a compilation error, print a warning
111
        . get root cursor and recurse
112
        . for each STRUCT_DECL, register a new struct type
113
        . for each UNION_DECL, register a new union type
114
        . for each TYPEDEF_DECL, register a new alias/typdef to the underlying type
115
            - underlying type is cursor.type.get_declaration() for Record
116
        . for each VAR_DECL, register a Variable
117
        . for each TYPEREF ??
118
        """
119
        if os.path.abspath(filename) in self.__processed_location:
1✔
120
            return
×
121
        index = Index.create()
1✔
122
        translation_unit = index.parse(filename, self.flags, options=self.tu_options)
1✔
123
        if not translation_unit:
1✔
124
            log.warning("unable to load input")
×
125
            return
×
126
        self._parse_tu_diagnostics(translation_unit, filename)
1✔
127
        self.tu = translation_unit
1✔
128
        root = self.tu.cursor
1✔
129
        for node in root.get_children():
1✔
130
            self.start_element(node)
1✔
131
        return
1✔
132

133
    def parse_string(self, input_data, lang="c", all_warnings=False, flags=None):
1✔
134
        """Use this parser on a memory string/file, instead of a file on disk"""
135
        translation_unit = util.get_tu(input_data, lang, all_warnings, flags)
1✔
136
        self._parse_tu_diagnostics(translation_unit, "memory_input.c")
1✔
137
        self.tu = translation_unit
1✔
138
        root = self.tu.cursor
1✔
139
        for node in root.get_children():
1✔
140
            self.start_element(node)
1✔
141

142
    @staticmethod
1✔
143
    def _parse_tu_diagnostics(translation_unit, input_filename):
1✔
144
        if len(translation_unit.diagnostics) == 0:
1✔
145
            return
1✔
146
        errors = []
1✔
147
        for diagnostic in translation_unit.diagnostics:
1✔
148
            msg = (
1✔
149
                f"{diagnostic.spelling} ({diagnostic.location.file}:{diagnostic.location.line}:"
150
                f"{diagnostic.location.column}) during processing {input_filename}"
151
            )
152
            log.warning(msg)
1✔
153
            if diagnostic.severity > 2:
1✔
154
                errors.append(msg)
1✔
155
        if len(errors) > 0:
1✔
156
            log.warning("Source code has %d error. Please fix.", len(errors))
1✔
157
            # code.interact(local=locals())
158
            raise InvalidTranslationUnitException(errors[0])
1✔
159

160
    def start_element(self, node):
1✔
161
        """Recurses in children of this node"""
162
        if node is None:
1✔
163
            return None
×
164

165
        if self.__filter_location is not None:
1✔
166
            # dont even parse includes.
167
            # FIXME: go back on dependencies ?
168
            if node.location.file is None:
1✔
169
                return None
×
170
            filepath = os.path.abspath(node.location.file.name)
1✔
171
            if filepath not in self.__filter_location:
1✔
172
                if not filepath.startswith("/usr"):
×
173
                    log.debug("skipping include '%s'", filepath)
×
174
                return None
×
175
        # find and call the handler for this element
176
        log.debug(
1✔
177
            "%s:%d: Found a %s|%s|%s",
178
            node.location.file,
179
            node.location.line,
180
            node.kind.name,
181
            node.displayname,
182
            node.spelling,
183
        )
184
        # build stuff.
185
        try:
1✔
186
            stop_recurse = self.parse_cursor(node)
1✔
187
            if node.location.file is not None:
1✔
188
                filepath = os.path.abspath(node.location.file.name)
1✔
189
                self.__processed_location.add(filepath)
1✔
190
            # Signature of parse_cursor is:
191
            # if the fn returns True, do not recurse into children.
192
            # anything else will be ignored.
193
            if stop_recurse is not False:  # True:
1✔
194
                return None
1✔
195
            # if fn returns something, if this element has children, treat
196
            # them.
197
            for child in node.get_children():
1✔
198
                self.start_element(child)
×
199
        except InvalidDefinitionError:
1✔
200
            log.exception("Invalid definition")
×
201
            # if the definition is invalid
202
        # startElement returns None.
203
        return None
1✔
204

205
    def register(self, name, obj):
1✔
206
        """Registers an unique type description"""
207
        if (name, obj) in self.all_set:
1✔
208
            log.debug("register: %s already defined: %s", name, obj.name)
×
209
            return self.all[name]
×
210
        if name in self.all:
1✔
211
            if not isinstance(self.all[name], typedesc.Structure) or (self.all[name].members is not None):
1✔
212
                # code.interact(local=locals())
213
                raise DuplicateDefinitionException(
1✔
214
                    f"register: {name} which has a previous incompatible definition: {obj.name}"
215
                    f"\ndefined here: {obj.location}"
216
                    f"\npreviously defined here: {self.all[name].location}"
217
                )
218
            if isinstance(self.all[name], typedesc.Structure) and (self.all[name].members is None):
1✔
219
                return obj
1✔
220
        log.debug("register: %s ", name)
1✔
221
        self.all[name] = obj
1✔
222
        self.all_set.add((name, obj))
1✔
223
        return obj
1✔
224

225
    def get_registered(self, name):
1✔
226
        """Returns a registered type description"""
227
        return self.all[name]
1✔
228

229
    def is_registered(self, name):
1✔
230
        """Checks if a named type description is registered"""
231
        return name in self.all
1✔
232

233
    def remove_registered(self, name):
1✔
234
        """Removes a named type"""
235
        log.debug("Unregister %s", name)
1✔
236
        self.all_set.remove((name, self.all[name]))
1✔
237
        del self.all[name]
1✔
238

239
    def make_ctypes_convertor(self, _flags):
1✔
240
        """
241
        Fix clang types to ctypes conversion for this parsing instance.
242
        Some architecture dependent size types have to be changed if the target
243
        architecture is not the same as local
244
        """
245
        # NOTE: one could also use the __SIZEOF_x__ MACROs to obtain sizes.
246
        translation_unit = util.get_tu(
1✔
247
            """
248
typedef short short_t;
249
typedef int int_t;
250
typedef long long_t;
251
typedef long long longlong_t;
252
typedef float float_t;
253
typedef double double_t;
254
typedef long double longdouble_t;
255
typedef void* pointer_t;""",
256
            flags=_flags,
257
        )
258
        size = util.get_cursor(translation_unit, "short_t").type.get_size() * 8
1✔
259
        self.ctypes_typename[TypeKind.SHORT] = f"c_int{size:d}"
1✔
260
        self.ctypes_typename[TypeKind.USHORT] = f"c_uint{size:d}"
1✔
261
        self.ctypes_sizes[TypeKind.SHORT] = size
1✔
262
        self.ctypes_sizes[TypeKind.USHORT] = size
1✔
263

264
        size = util.get_cursor(translation_unit, "int_t").type.get_size() * 8
1✔
265
        self.ctypes_typename[TypeKind.INT] = f"c_int{size:d}"
1✔
266
        self.ctypes_typename[TypeKind.UINT] = f"c_uint{size:d}"
1✔
267
        self.ctypes_sizes[TypeKind.INT] = size
1✔
268
        self.ctypes_sizes[TypeKind.UINT] = size
1✔
269

270
        size = util.get_cursor(translation_unit, "long_t").type.get_size() * 8
1✔
271
        self.ctypes_typename[TypeKind.LONG] = f"c_int{size:d}"
1✔
272
        self.ctypes_typename[TypeKind.ULONG] = f"c_uint{size:d}"
1✔
273
        self.ctypes_sizes[TypeKind.LONG] = size
1✔
274
        self.ctypes_sizes[TypeKind.ULONG] = size
1✔
275

276
        size = util.get_cursor(translation_unit, "longlong_t").type.get_size() * 8
1✔
277
        self.ctypes_typename[TypeKind.LONGLONG] = f"c_int{size:d}"
1✔
278
        self.ctypes_typename[TypeKind.ULONGLONG] = f"c_uint{size:d}"
1✔
279
        self.ctypes_sizes[TypeKind.LONGLONG] = size
1✔
280
        self.ctypes_sizes[TypeKind.ULONGLONG] = size
1✔
281

282
        # FIXME : Float && http://en.wikipedia.org/wiki/Long_double
283
        size0 = util.get_cursor(translation_unit, "float_t").type.get_size() * 8
1✔
284
        size1 = util.get_cursor(translation_unit, "double_t").type.get_size() * 8
1✔
285
        size2 = util.get_cursor(translation_unit, "longdouble_t").type.get_size() * 8
1✔
286
        # 2014-01 stop generating crap.
287
        # 2015-01 reverse until better solution is found
288
        # the idea is that you cannot assume a c_double will be same format as a c_long_double.
289
        # at least this pass size TU
290
        if size1 != size2:
1✔
291
            self.ctypes_typename[TypeKind.LONGDOUBLE] = "c_long_double_t"
1✔
292
        else:
293
            self.ctypes_typename[TypeKind.LONGDOUBLE] = "c_double"
1✔
294

295
        self.ctypes_sizes[TypeKind.FLOAT] = size0
1✔
296
        self.ctypes_sizes[TypeKind.DOUBLE] = size1
1✔
297
        self.ctypes_sizes[TypeKind.LONGDOUBLE] = size2
1✔
298

299
        # save the target pointer size.
300
        size = util.get_cursor(translation_unit, "pointer_t").type.get_size() * 8
1✔
301
        self.ctypes_sizes[TypeKind.POINTER] = size
1✔
302
        self.ctypes_sizes[TypeKind.NULLPTR] = size
1✔
303

304
        log.debug(
1✔
305
            "ARCH sizes: long:%s longdouble:%s",
306
            self.ctypes_typename[TypeKind.LONG],
307
            self.ctypes_typename[TypeKind.LONGDOUBLE],
308
        )
309
        return
1✔
310

311
    def get_ctypes_name(self, typekind):
1✔
312
        return self.ctypes_typename[typekind]
1✔
313

314
    def get_ctypes_size(self, typekind):
1✔
315
        return self.ctypes_sizes[typekind]
1✔
316

317
    def parse_cursor(self, cursor):
1✔
318
        """Forward parsing calls to dedicated CursorKind Handlder"""
319
        return self.cursorkind_handler.parse_cursor(cursor)
1✔
320

321
    def parse_cursor_type(self, _cursor_type):
1✔
322
        """Forward parsing calls to dedicated TypeKind Handlder"""
323
        return self.typekind_handler.parse_cursor_type(_cursor_type)
1✔
324

325
    ###########################################################################
326

327
    ################
328

329
    def get_macros(self, text):
1✔
330
        if text is None:
1✔
331
            return
1✔
332
        text = "".join(text)
×
333
        # preprocessor definitions that look like macros with one or more
334
        # arguments
335
        for macro in text.splitlines():
×
336
            name, body = macro.split(None, 1)
×
337
            name, args = name.split("(", 1)
×
338
            args = f"({args}"
×
339
            self.all[name] = typedesc.Macro(name, args, body)
×
340

341
    def get_aliases(self, text, namespace):
1✔
342
        if text is None:
1✔
343
            return
1✔
344
        # preprocessor definitions that look like aliases:
345
        #  #define A B
346
        text = "".join(text)
×
347
        aliases = {}
×
348
        for alias in text.splitlines():
×
349
            name, value = alias.split(None, 1)
×
350
            alias = typedesc.Alias(name, value)
×
351
            aliases[name] = alias
×
352
            self.all[name] = alias
×
353

354
        for name, alias in aliases.items():
×
355
            value = alias.alias
×
356
            # the value should be either in namespace...
357
            if value in namespace:
×
358
                # set the type
359
                alias.typ = namespace[value]
×
360
            # or in aliases...
361
            elif value in aliases:
×
362
                alias.typ = aliases[value]
×
363
            # or unknown.
364
            else:
365
                # not known
366
                # print "skip %s = %s" % (name, value)
367
                pass
×
368

369
    def get_result(self):
1✔
370
        # all of these should register()
371
        interesting = (
1✔
372
            typedesc.Typedef,
373
            typedesc.Enumeration,
374
            typedesc.EnumValue,
375
            typedesc.Function,
376
            typedesc.Structure,
377
            typedesc.Union,
378
            typedesc.Variable,
379
            typedesc.Macro,
380
            typedesc.Alias,
381
            typedesc.FunctionType,
382
        )
383
        # typedesc.Field) #???
384

385
        self.get_macros(self.cpp_data.get("functions"))
1✔
386
        # fix all objects after that all are resolved
387
        remove = []
1✔
388
        for _id, _item in self.all.items():
1✔
389
            if _item is None:
1✔
390
                log.warning("ignoring %s", _id)
×
391
                continue
×
392
            location = getattr(_item, "location", None)
1✔
393
            # FIXME , why do we get different location types
394
            if location and hasattr(location, "file"):
1✔
395
                _item.location = location.file.name, location.line
×
396
                log.error("%s %s came in with a SourceLocation", _id, _item)
×
397
            elif location is None:
1✔
398
                # FIXME make this optional to be able to see internals
399
                # FIXME macro/alias are here
400
                log.warning("No source location in %s - ignoring", _id)
1✔
401
                remove.append(_id)
1✔
402

403
        for _x in remove:
1✔
404
            self.remove_registered(_x)
1✔
405

406
        # Now we can build the namespace.
407
        namespace = {}
1✔
408
        for i in self.all.values():
1✔
409
            if not isinstance(i, interesting):
1✔
410
                log.debug("ignoring %s", i)
×
411
                continue  # we don't want these
×
412
            name = getattr(i, "name", None)
1✔
413
            if name is not None:
1✔
414
                namespace[name] = i
1✔
415
        self.get_aliases(self.cpp_data.get("aliases"), namespace)
1✔
416

417
        result = []
1✔
418
        for i in self.all.values():
1✔
419
            if isinstance(i, interesting):
1✔
420
                result.append(i)
1✔
421

422
        log.debug("parsed items order: %s", result)
1✔
423
        return result
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc