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

trolldbois / ctypeslib / 4773564691

pending completion
4773564691

push

github

Loic Jaquemet
try without baipp

1601 of 1912 relevant lines covered (83.73%)

50.0 hits per line

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

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

3
import logging
4
import os
5
import collections
6

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

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

18
log = logging.getLogger('clangparser')
19

20

21
class Clang_Parser(object):
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 = {"Enumeration", "Function", "FunctionType",
48✔
31
                  "OperatorFunction", "Method", "Constructor",
12✔
32
                  "Destructor", "OperatorMethod",
12✔
33
                  "Converter"}
12✔
34

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

63
    def __init__(self, flags):
48✔
64
        self.all = collections.OrderedDict()
48✔
65
        # a shortcut to identify registered decl in cases of records
66
        self.all_set = set()
48✔
67
        self.cpp_data = {}
48✔
68
        self._unhandled = []
48✔
69
        self.fields = {}
48✔
70
        self.tu = None
48✔
71
        self.flags = flags
48✔
72
        self.ctypes_sizes = {}
48✔
73
        self.init_parsing_options()
48✔
74
        self.make_ctypes_convertor(flags)
48✔
75
        self.cursorkind_handler = cursorhandler.CursorHandler(self)
48✔
76
        self.typekind_handler = typehandler.TypeHandler(self)
48✔
77
        self.__filter_location = None
48✔
78
        self.__processed_location = set()
48✔
79

80
    def init_parsing_options(self):
48✔
81
        """Set the Translation Unit to skip functions bodies per default."""
82
        self.tu_options = TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
12✔
83

84
    def activate_macros_parsing(self):
12✔
85
        """Activates the detailled code parsing options in the Translation
86
        Unit."""
87
        self.tu_options |= TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD
60✔
88

89
    def activate_comment_parsing(self):
60✔
90
        """Activates the comment parsing options in the Translation Unit."""
91
        self.tu_options |= TranslationUnit.PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION
92

93
    def deactivate_function_body_parsing(self):
94
        self.tu_options |= TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
95

96
    def filter_location(self, src_files):
97
        self.__filter_location = list(
98
            map(lambda f: os.path.abspath(f), src_files))
99

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

126
    def parse_string(self, input_data, lang='c', all_warnings=False, flags=None):
60✔
127
        """Use this parser on a memory string/file, instead of a file on disk"""
128
        tu = util.get_tu(input_data, lang, all_warnings, flags)
129
        self._parse_tu_diagnostics(tu, "memory_input.c")
130
        self.tu = tu
131
        root = self.tu.cursor
132
        for node in root.get_children():
133
            self.startElement(node)
134
        return
135

136
    @staticmethod
137
    def _parse_tu_diagnostics(tu, input_filename):
138
        if len(tu.diagnostics) == 0:
139
            return
140
        errors = []
141
        for x in tu.diagnostics:
142
            msg = "{} ({}:{}:{}) during processing {}".format(
143
                x.spelling, x.location.file,
144
                x.location.line, x.location.column, input_filename)
145
            log.warning(msg)
146
            if x.severity > 2:
147
                errors.append(msg)
148
        if len(errors) > 0:
149
            log.warning("Source code has %d error. Please fix.", len(errors))
150
            # code.interact(local=locals())
151
            raise InvalidTranslationUnitException(errors[0])
152

153
    def startElement(self, node):
154
        """Recurses in children of this node"""
155
        if node is None:
60✔
156
            return
×
157

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

198
    def register(self, name, obj):
60✔
199
        """Registers an unique type description"""
200
        if (name, obj) in self.all_set:
201
            log.debug('register: %s already defined: %s', name, obj.name)
202
            return self.all[name]
203
        if name in self.all:
204
            if not isinstance(self.all[name], typedesc.Structure) or (
205
                    self.all[name].members is not None):
206
                # code.interact(local=locals())
207
                raise DuplicateDefinitionException(
208
                    'register: %s which has a previous incompatible definition: %s'
209
                    '\ndefined here: %s'
210
                    '\npreviously defined here: %s'
211
                    % (name, obj.name, obj.location, self.all[name].location))
212
            if isinstance(self.all[name], typedesc.Structure) and (
213
                    self.all[name].members is None):
214
                return obj
215
        log.debug('register: %s ', name)
216
        self.all[name] = obj
217
        self.all_set.add((name, obj))
218
        return obj
219

220
    def get_registered(self, name):
221
        """Returns an registered type description"""
222
        return self.all[name]
60✔
223

224
    def is_registered(self, name):
60✔
225
        """Checks if a named type description is registered"""
226
        return name in self.all
227

228
    def remove_registered(self, name):
229
        """Removes a named type"""
230
        log.debug('Unregister %s', name)
60✔
231
        self.all_set.remove((name, self.all[name]))
60✔
232
        del self.all[name]
60✔
233

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

256
        size = util.get_cursor(tu, 'int_t').type.get_size() * 8
60✔
257
        self.ctypes_typename[TypeKind.INT] = 'c_int%d' % (size)
60✔
258
        self.ctypes_typename[TypeKind.UINT] = 'c_uint%d' % (size)
60✔
259
        self.ctypes_sizes[TypeKind.INT] = size
60✔
260
        self.ctypes_sizes[TypeKind.UINT] = size
60✔
261

262
        size = util.get_cursor(tu, 'long_t').type.get_size() * 8
60✔
263
        self.ctypes_typename[TypeKind.LONG] = 'c_int%d' % (size)
60✔
264
        self.ctypes_typename[TypeKind.ULONG] = 'c_uint%d' % (size)
60✔
265
        self.ctypes_sizes[TypeKind.LONG] = size
60✔
266
        self.ctypes_sizes[TypeKind.ULONG] = size
60✔
267

268
        size = util.get_cursor(tu, 'longlong_t').type.get_size() * 8
60✔
269
        self.ctypes_typename[TypeKind.LONGLONG] = 'c_int%d' % (size)
60✔
270
        self.ctypes_typename[TypeKind.ULONGLONG] = 'c_uint%d' % (size)
60✔
271
        self.ctypes_sizes[TypeKind.LONGLONG] = size
60✔
272
        self.ctypes_sizes[TypeKind.ULONGLONG] = size
60✔
273

274
        # FIXME : Float && http://en.wikipedia.org/wiki/Long_double
275
        size0 = util.get_cursor(tu, 'float_t').type.get_size() * 8
60✔
276
        size1 = util.get_cursor(tu, 'double_t').type.get_size() * 8
60✔
277
        size2 = util.get_cursor(tu, 'longdouble_t').type.get_size() * 8
60✔
278
        # 2014-01 stop generating crap.
279
        # 2015-01 reverse until better solution is found
280
        # the idea is that a you cannot assume a c_double will be same format as a c_long_double.
281
        # at least this pass size TU
282
        if size1 != size2:
60✔
283
            self.ctypes_typename[TypeKind.LONGDOUBLE] = 'c_long_double_t'
60✔
284
        else:
285
            self.ctypes_typename[TypeKind.LONGDOUBLE] = 'c_double'
60✔
286

287
        self.ctypes_sizes[TypeKind.FLOAT] = size0
60✔
288
        self.ctypes_sizes[TypeKind.DOUBLE] = size1
60✔
289
        self.ctypes_sizes[TypeKind.LONGDOUBLE] = size2
60✔
290

291
        # save the target pointer size.
292
        size = util.get_cursor(tu, 'pointer_t').type.get_size() * 8
60✔
293
        self.ctypes_sizes[TypeKind.POINTER] = size
60✔
294
        self.ctypes_sizes[TypeKind.NULLPTR] = size
60✔
295

296
        log.debug('ARCH sizes: long:%s longdouble:%s',
60✔
297
                  self.ctypes_typename[TypeKind.LONG],
60✔
298
                  self.ctypes_typename[TypeKind.LONGDOUBLE])
60✔
299
        return
60✔
300

301
    def get_ctypes_name(self, typekind):
60✔
302
        return self.ctypes_typename[typekind]
60✔
303

304
    def get_ctypes_size(self, typekind):
60✔
305
        return self.ctypes_sizes[typekind]
60✔
306

307
    def parse_cursor(self, cursor):
60✔
308
        """Forward parsing calls to dedicated CursorKind Handlder"""
309
        return self.cursorkind_handler.parse_cursor(cursor)
310

311
    def parse_cursor_type(self, _cursor_type):
312
        """Forward parsing calls to dedicated TypeKind Handlder"""
313
        return self.typekind_handler.parse_cursor_type(_cursor_type)
60✔
314

315
    ###########################################################################
316

317
    ################
318

319
    def get_macros(self, text):
60✔
320
        if text is None:
60✔
321
            return
60✔
322
        text = "".join(text)
×
323
        # preprocessor definitions that look like macros with one or more
324
        # arguments
325
        for m in text.splitlines():
×
326
            name, body = m.split(None, 1)
×
327
            name, args = name.split("(", 1)
×
328
            args = "(%s" % args
×
329
            self.all[name] = typedesc.Macro(name, args, body)
×
330

331
    def get_aliases(self, text, namespace):
60✔
332
        if text is None:
60✔
333
            return
60✔
334
        # preprocessor definitions that look like aliases:
335
        #  #define A B
336
        text = "".join(text)
×
337
        aliases = {}
×
338
        for a in text.splitlines():
×
339
            name, value = a.split(None, 1)
×
340
            a = typedesc.Alias(name, value)
×
341
            aliases[name] = a
×
342
            self.all[name] = a
×
343

344
        for name, a in aliases.items():
×
345
            value = a.alias
×
346
            # the value should be either in namespace...
347
            if value in namespace:
×
348
                # set the type
349
                a.typ = namespace[value]
×
350
            # or in aliases...
351
            elif value in aliases:
×
352
                a.typ = aliases[value]
×
353
            # or unknown.
354
            else:
×
355
                # not known
356
                # print "skip %s = %s" % (name, value)
357
                pass
×
358

359
    def get_result(self):
60✔
360
        # all of these should register()
361
        interesting = (typedesc.Typedef, typedesc.Enumeration, typedesc.EnumValue,
60✔
362
                       typedesc.Function, typedesc.Structure, typedesc.Union,
60✔
363
                       typedesc.Variable, typedesc.Macro, typedesc.Alias,
60✔
364
                       typedesc.FunctionType)
60✔
365
        # typedesc.Field) #???
366

367
        self.get_macros(self.cpp_data.get("functions"))
60✔
368
        # fix all objects after that all are resolved
369
        remove = []
60✔
370
        for _id, _item in self.all.items():
60✔
371
            if _item is None:
60✔
372
                log.warning('ignoring %s', _id)
×
373
                continue
×
374
            location = getattr(_item, "location", None)
60✔
375
            # FIXME , why do we get different location types
376
            if location and hasattr(location, 'file'):
60✔
377
                _item.location = location.file.name, location.line
×
378
                log.error('%s %s came in with a SourceLocation', _id, _item)
×
379
            elif location is None:
60✔
380
                # FIXME make this optional to be able to see internals
381
                # FIXME macro/alias are here
382
                log.warning("No source location in %s - ignoring", _id)
60✔
383
                remove.append(_id)
60✔
384

385
        for _x in remove:
60✔
386
            self.remove_registered(_x)
60✔
387

388
        # Now we can build the namespace.
389
        namespace = {}
60✔
390
        for i in self.all.values():
60✔
391
            if not isinstance(i, interesting):
60✔
392
                log.debug('ignoring %s', i)
×
393
                continue  # we don't want these
×
394
            name = getattr(i, "name", None)
60✔
395
            if name is not None:
60✔
396
                namespace[name] = i
60✔
397
        self.get_aliases(self.cpp_data.get("aliases"), namespace)
60✔
398

399
        result = []
60✔
400
        for i in self.all.values():
60✔
401
            if isinstance(i, interesting):
60✔
402
                result.append(i)
60✔
403

404
        log.debug("parsed items order: %s", result)
60✔
405
        return result
60✔
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