• 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

90.32
/ctypeslib/codegen/cursorhandler.py
1
"""Handler for Cursor nodes from the clang AST tree."""
2

3
import logging
1✔
4
import re
1✔
5

6
from clang.cindex import CursorKind, LinkageKind, TypeKind, TokenKind
1✔
7

8
from ctypeslib.codegen import typedesc, util
1✔
9
from ctypeslib.codegen.handler import ClangHandler
1✔
10
from ctypeslib.codegen.handler import CursorKindException
1✔
11
from ctypeslib.codegen.handler import DuplicateDefinitionException
1✔
12
from ctypeslib.codegen.handler import InvalidDefinitionError
1✔
13
from ctypeslib.codegen.util import log_entity
1✔
14

15
log = logging.getLogger('cursorhandler')
1✔
16

17

18
class CursorHandler(ClangHandler):
1✔
19
    """
20
    Factory objects that handles Cursor Kind and transform them into typedesc.
21

22
    # clang.cindex.CursorKind
23
    # Declarations: 1-39
24
    # Reference: 40-49
25
    # Invalids: 70-73
26
    # Expressions: 100-143
27
    # Statements: 200-231
28
    # Root Translation unit: 300
29
    # Attributes: 400-403
30
    # Preprocessing: 500-503
31
    """
32

33
    def __init__(self, parser):
1✔
34
        ClangHandler.__init__(self, parser)
1✔
35

36
    def parse_cursor(self, cursor):
1✔
37
        mth = getattr(self, cursor.kind.name)
1✔
38
        return mth(cursor)
1✔
39

40
    ##########################################################################
41
    ##### CursorKind handlers#######
42
    ##########################################################################
43

44
    ###########################################
45
    # ATTRIBUTES
46

47
    # @log_entity
48
    # def UNEXPOSED_ATTR(self, cursor):
49
    # FIXME: do we do something with these ?
50
    # parent = cursor.semantic_parent
51
    # print 'parent is',parent.displayname, parent.location, parent.extent
52
    # TODO until attr is exposed by clang:
53
    # readlines()[extent] .split(' ') | grep {inline,packed}
54
    #    return
55

56
    # @log_entity
57
    # def PACKED_ATTR(self, cursor):
58
    # FIXME: do we do something with these ?
59
    # parent = cursor.semantic_parent
60
    # print 'parent is',parent.displayname, parent.location, parent.extent
61
    # TODO until attr is exposed by clang:
62
    # readlines()[extent] .split(' ') | grep {inline,packed}
63

64
    #    return
65

66
    ################################
67
    # EXPRESSIONS handlers
68

69
    # clang does not expose some types for some expression.
70
    # Example: the type of token group in a Char_s or char variable.
71
    # Counter example: The type of integer literal to a (int) variable.
72
    @log_entity
1✔
73
    def UNEXPOSED_EXPR(self, cursor):  # noqa
1✔
74
        ret = []
1✔
75
        for child in cursor.get_children():
1✔
76
            ret.append(self.parse_cursor(child))
1✔
77
        if len(ret) == 1:
1✔
78
            return ret[0]
1✔
79
        return ret
×
80

81
    @log_entity
1✔
82
    def DECL_REF_EXPR(self, cursor):  # noqa
1✔
83
        return cursor.displayname
×
84

85
    @log_entity
1✔
86
    def INIT_LIST_EXPR(self, cursor):  # noqa
1✔
87
        """Returns a list of literal values."""
88
        values = [self.parse_cursor(child)
1✔
89
                  for child in list(cursor.get_children())]
90
        return values
1✔
91

92
    ################################
93
    # STATEMENTS handlers
94

95
    # Do not traverse into function bodies and other compound statements
96
    # now fixed by TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
97
    COMPOUND_STMT = ClangHandler._do_nothing
1✔
98

99
    @log_entity
1✔
100
    def NAMESPACE(self, cursor):  # noqa
1✔
101
        for child in cursor.get_children():
1✔
102
            self.parse_cursor(child)  # FIXME, where is the starElement
1✔
103

104
    ################################
105
    # TYPE REFERENCES handlers
106

107
    @log_entity
1✔
108
    def TYPE_REF(self, cursor):  # noqa
1✔
109
        name = self.get_unique_name(cursor)
×
110
        if self.is_registered(name):
×
111
            return self.get_registered(name)
×
112
        # log.warning('TYPE_REF with no saved decl in self.all')
113
        # return None
114
        # Should probably never get here.
115
        # I'm a field. ?
116
        _definition = cursor.get_definition()
×
117
        if _definition is None:
×
118
            # log.warning('no definition in this type_ref ?')
119
            # code.interact(local=locals())
120
            # raise IOError('I doubt this case is possible')
121
            _definition = cursor.type.get_declaration()
×
122
        return None  # self.parse_cursor(_definition)
×
123

124
    ################################
125
    # DECLARATIONS handlers
126
    #
127
    # UNEXPOSED_DECL are unexposed by clang. Go through the node's children.
128
    # VAR_DECL are Variable declarations. Initialisation value(s) are collected
129
    #          within _get_var_decl_init_value
130
    #
131

132
    NO_DECL_FOUND = ClangHandler._do_nothing
1✔
133

134
    UNEXPOSED_DECL = ClangHandler._pass_through_children
1✔
135
    """Undexposed declaration. Go and see children. """
1✔
136

137
    @log_entity
1✔
138
    def ENUM_CONSTANT_DECL(self, cursor):  # noqa
1✔
139
        """Gets the enumeration values"""
140
        name = cursor.displayname
1✔
141
        value = cursor.enum_value
1✔
142
        pname = self.get_unique_name(cursor.semantic_parent)
1✔
143
        parent = self.get_registered(pname)
1✔
144
        obj = typedesc.EnumValue(name, value, parent)
1✔
145
        parent.add_value(obj)
1✔
146
        return obj
1✔
147

148
    @log_entity
1✔
149
    def ENUM_DECL(self, cursor):  # noqa
1✔
150
        """Gets the enumeration declaration."""
151
        name = self.get_unique_name(cursor)
1✔
152
        if self.is_registered(name):
1✔
153
            return self.get_registered(name)
×
154
        align = cursor.type.get_align()
1✔
155
        size = cursor.type.get_size()
1✔
156
        obj = self.register(name, typedesc.Enumeration(name, size, align))
1✔
157
        self.set_location(obj, cursor)
1✔
158
        self.set_comment(obj, cursor)
1✔
159
        # parse all children
160
        for child in cursor.get_children():
1✔
161
            self.parse_cursor(child)  # FIXME, where is the starElement
1✔
162
        return obj
1✔
163

164
    @log_entity
1✔
165
    def FUNCTION_DECL(self, cursor):  # noqa
1✔
166
        """Handles function declaration"""
167
        # FIXME to UT
168
        name = self.get_unique_name(cursor)
1✔
169
        if self.is_registered(name):
1✔
170
            return self.get_registered(name)
1✔
171
        returns = self.parse_cursor_type(cursor.type.get_result())
1✔
172
        attributes = []
1✔
173
        extern = False
1✔
174
        obj = typedesc.Function(name, returns, attributes, extern)
1✔
175
        for arg in cursor.get_arguments():
1✔
176
            arg_obj = self.parse_cursor(arg)
1✔
177
            # if arg_obj is None:
178
            #    code.interact(local=locals())
179
            obj.add_argument(arg_obj)
1✔
180
        # code.interact(local=locals())
181
        self.register(name, obj)
1✔
182
        self.set_location(obj, cursor)
1✔
183
        self.set_comment(obj, cursor)
1✔
184
        return obj
1✔
185

186
    @log_entity
1✔
187
    def PARM_DECL(self, cursor):  # noqa
1✔
188
        """Handles parameter declarations."""
189
        # try and get the type. If unexposed, The canonical type will work.
190
        _type = cursor.type
1✔
191
        _name = cursor.spelling
1✔
192
        if (self.is_array_type(_type) or
1✔
193
                self.is_fundamental_type(_type) or
194
                self.is_pointer_type(_type) or
195
                self.is_unexposed_type(_type)):
196
            _argtype = self.parse_cursor_type(_type)
1✔
197
        else:  # FIXME: Which UT/case ? size_t in stdio.h for example.
198
            _argtype_decl = _type.get_declaration()
1✔
199
            _argtype_name = self.get_unique_name(_argtype_decl)
1✔
200
            if not self.is_registered(_argtype_name):
1✔
201
                log.info('This param type is not declared: %s', _argtype_name)
×
202
                _argtype = self.parse_cursor_type(_type)
×
203
            else:
204
                _argtype = self.get_registered(_argtype_name)
1✔
205
        obj = typedesc.Argument(_name, _argtype)
1✔
206
        self.set_location(obj, cursor)
1✔
207
        self.set_comment(obj, cursor)
1✔
208
        return obj
1✔
209

210
    @log_entity
1✔
211
    def TYPEDEF_DECL(self, cursor):  # noqa
1✔
212
        """
213
        Handles typedef statements.
214
        Gets Type from cache if we known it. Add it to cache otherwise.
215
        # typedef of an enum
216
        """
217
        name = self.get_unique_name(cursor)
1✔
218
        # if the typedef is known, get it from cache
219
        if self.is_registered(name):
1✔
220
            return self.get_registered(name)
1✔
221
        # use the canonical type directly.
222
        _type = cursor.type.get_canonical()
1✔
223
        log.debug("TYPEDEF_DECL: name:%s", name)
1✔
224
        log.debug("TYPEDEF_DECL: typ.kind.displayname:%s", _type.kind)
1✔
225

226
        # For all types (array, fundament, pointer, others), get the type
227
        p_type = self.parse_cursor_type(_type)
1✔
228
        if not isinstance(p_type, typedesc.T):
1✔
229
            log.error(
×
230
                'Bad TYPEREF parsing in TYPEDEF_DECL: %s',
231
                _type.spelling)
232
            # import code
233
            # code.interact(local=locals())
234
            raise TypeError(
×
235
                'Bad TYPEREF parsing in TYPEDEF_DECL: %s' %
236
                _type.spelling)
237
        # register the type
238
        obj = self.register(name, typedesc.Typedef(name, p_type))
1✔
239
        self.set_location(obj, cursor)
1✔
240
        self.set_comment(obj, cursor)
1✔
241
        return obj
1✔
242

243
    @log_entity
1✔
244
    def VAR_DECL(self, cursor):  # noqa
1✔
245
        """Handles Variable declaration."""
246
        # get the name
247
        name = self.get_unique_name(cursor)
1✔
248
        log.debug('VAR_DECL: name: %s', name)
1✔
249
        # Check for a previous declaration in the register
250
        if self.is_registered(name):
1✔
251
            return self.get_registered(name)
×
252
        # get the typedesc object
253
        _type, extern = self._VAR_DECL_type(cursor)
1✔
254
        # transform the ctypes values into ctypeslib
255
        init_value = self._VAR_DECL_value(cursor, _type)
1✔
256
        # finished
257
        log.debug('VAR_DECL: _type:%s', _type.name)
1✔
258
        log.debug('VAR_DECL: _init:%s', init_value)
1✔
259
        log.debug('VAR_DECL: location:%s', getattr(cursor, 'location'))
1✔
260
        obj = self.register(name, typedesc.Variable(name, _type, init_value, extern))
1✔
261
        self.set_location(obj, cursor)
1✔
262
        self.set_comment(obj, cursor)
1✔
263
        return True
1✔
264

265
    def _VAR_DECL_type(self, cursor):  # noqa
1✔
266
        """Generates a typedesc object from a Variable declaration."""
267
        # Get the type
268
        _ctype = cursor.type.get_canonical()
1✔
269
        extern = cursor.linkage in (LinkageKind.EXTERNAL, LinkageKind.UNIQUE_EXTERNAL)  # noqa
1✔
270
        log.debug('VAR_DECL: _ctype: %s ', _ctype.kind)
1✔
271
        # FIXME: Need working int128, long_double, etc.
272
        if self.is_fundamental_type(_ctype):
1✔
273
            ctypesname = self.get_ctypes_name(_ctype.kind)
1✔
274
            _type = typedesc.FundamentalType(ctypesname, 0, 0)
1✔
275
        elif self.is_unexposed_type(_ctype):
1✔
276
            st = 'PATCH NEEDED: %s type is not exposed by clang' % (
×
277
                self.get_unique_name(cursor))
278
            log.error(st)
×
279
            raise RuntimeError(st)
×
280
        elif self.is_array_type(_ctype) or _ctype.kind == TypeKind.RECORD:  # noqa
1✔
281
            _type = self.parse_cursor_type(_ctype)
1✔
282
        elif self.is_pointer_type(_ctype):
1✔
283
            # for example, extern Function pointer
284
            if self.is_unexposed_type(_ctype.get_pointee()):
1✔
285
                _type = self.parse_cursor_type(
×
286
                    _ctype.get_canonical().get_pointee())
287
            elif _ctype.get_pointee().kind == TypeKind.FUNCTIONPROTO:  # noqa
1✔
288
                # Function pointers
289
                # Arguments are handled in here
290
                _type = self.parse_cursor_type(_ctype.get_pointee())
1✔
291
            else:  # Pointer to Fundamental types, structs....
292
                _type = self.parse_cursor_type(_ctype)
1✔
293
        else:
294
            # What else ?
295
            raise NotImplementedError(
1✔
296
                'What other type of variable? %s' %
297
                _ctype.kind)
298
        log.debug('VAR_DECL: _type: %s ', _type)
1✔
299
        return _type, extern
1✔
300

301
    def _VAR_DECL_value(self, cursor, _type):  # noqa
1✔
302
        """Handles Variable value initialization."""
303
        # always expect list [(k,v)] as init value.from list(cursor.get_children())
304
        # get the init_value and special cases
305
        init_value = self._get_var_decl_init_value(cursor.type,
1✔
306
                                                   list(cursor.get_children()))
307
        _ctype = cursor.type.get_canonical()
1✔
308
        if self.is_unexposed_type(_ctype):
1✔
309
            # string are not exposed
310
            init_value = '%s # UNEXPOSED TYPE. PATCH NEEDED.' % init_value
×
311
        elif (self.is_pointer_type(_ctype) and
1✔
312
                      _ctype.get_pointee().kind == TypeKind.FUNCTIONPROTO):  # noqa
313
            # Function pointers argument are handled at type creation time
314
            # but we need to put a CFUNCTYPE as a value of the name variable
315
            init_value = _type
1✔
316
        elif self.is_array_type(_ctype):
1✔
317
            # an integer literal will be the size
318
            # a string literal will be the value
319
            # any list member will be children of a init_list_expr
320
            # FIXME Move that code into typedesc
321
            def countof(k, _value):
1✔
322
                return [item[0] for item in _value].count(k)
1✔
323

324
            if countof(CursorKind.INIT_LIST_EXPR, init_value) == 1:  # noqa
1✔
325
                init_value = dict(init_value)[CursorKind.INIT_LIST_EXPR]  # noqa
1✔
326
            elif countof(CursorKind.STRING_LITERAL, init_value) == 1:  # noqa
1✔
327
                # we have a initialised c_array
328
                init_value = dict(init_value)[CursorKind.STRING_LITERAL]  # noqa
1✔
329
            else:
330
                # ignore size alone
331
                init_value = []
1✔
332
            # check the array size versus elements.
333
            if _type.size < len(init_value):
1✔
334
                _type.size = len(init_value)
×
335
        elif init_value == []:
1✔
336
            # catch case.
337
            init_value = None
1✔
338
        else:
339
            log.debug('VAR_DECL: default init_value: %s', init_value)
1✔
340
            if len(init_value) > 0:
1✔
341
                init_value = init_value[0][1]
1✔
342
        return init_value
1✔
343

344
    def _get_var_decl_init_value(self, _ctype, children):
1✔
345
        """
346
        Gathers initialisation values by parsing children nodes of a VAR_DECL.
347
        """
348

349
        # FIXME TU for INIT_LIST_EXPR
350
        # FIXME: always return [(child.kind,child.value),...]
351
        # FIXME: simplify this redondant code.
352
        init_value = []
1✔
353
        children = list(children)  # weird requirement, list iterator error.
1✔
354
        log.debug('_get_var_decl_init_value: children #: %d', len(children))
1✔
355
        for child in children:
1✔
356
            # early stop cases.
357
            _tmp = None
1✔
358
            try:
1✔
359
                _tmp = self._get_var_decl_init_value_single(_ctype, child)
1✔
360
            except CursorKindException:
1✔
361
                log.debug(
1✔
362
                    '_get_var_decl_init_value: children init value skip on %s',
363
                    child.kind)
364
                continue
1✔
365
            if _tmp is not None:
1✔
366
                init_value.append(_tmp)
1✔
367
        return init_value
1✔
368

369
    def _get_var_decl_init_value_single(self, _ctype, child):
1✔
370
        """
371
        Handling of a single child for initialization value.
372
        Accepted types are expressions and declarations
373
        """
374
        init_value = None
1✔
375
        # FIXME: always return (child.kind, child.value)
376
        log.debug(
1✔
377
            '_get_var_decl_init_value_single: _ctype: %s Child.kind: %s',
378
            _ctype.kind,
379
            child.kind)
380
        # shorcuts.
381
        if not child.kind.is_expression() and not child.kind.is_declaration():
1✔
382
            raise CursorKindException(child.kind)
1✔
383
        if child.kind == CursorKind.CALL_EXPR:  # noqa
1✔
384
            raise CursorKindException(child.kind)
×
385
        # POD init values handling.
386
        # As of clang 3.3, int, double literals are exposed.
387
        # float, long double, char , char* are not exposed directly in level1.
388
        # but really it depends...
389
        if child.kind.is_unexposed():
1✔
390
            # recurse until we find a literal kind
391
            init_value = self._get_var_decl_init_value(_ctype, child.get_children())
1✔
392
            if len(init_value) == 0:
1✔
393
                init_value = None
×
394
            elif len(init_value) == 1:
1✔
395
                init_value = init_value[0]
1✔
396
            else:
397
                log.error('_get_var_decl_init_value_single: Unhandled case')
×
398
                assert len(init_value) <= 1
×
399
        # elif child.kind == CursorKind.STRING_LITERAL:
400
        #     _v = self._literal_handling(child)
401
        #     init_value = (child.kind, _v)
402
        else:  # literal or others
403
            _v = self.parse_cursor(child)
1✔
404
            if isinstance(_v, list) and len(_v) > 0 and child.kind not in [CursorKind.INIT_LIST_EXPR, CursorKind.STRING_LITERAL]:  # noqa
1✔
405
                log.warning('_get_var_decl_init_value_single: TOKENIZATION BUG CHECK: %s', _v)
×
406
                _v = _v[0]
×
407
            init_value = (child.kind, _v)
1✔
408
        log.debug('_get_var_decl_init_value_single: returns %s', str(init_value))
1✔
409
        return init_value
1✔
410

411
    def _clean_string_literal(self, cursor, value):
1✔
412
        # strip wchar_t type prefix for string/character
413
        # indicatively: u8 for utf-8, u for utf-16, U for utf32
414
        # assume that the source file is utf-8
415
        # utf-32 not supported in 2.7, lets keep all in utf8
416
        # string prefixes https://en.cppreference.com/w/cpp/language/string_literal
417
        # integer suffixes https://en.cppreference.com/w/cpp/language/integer_literal
418
        if cursor.kind in [CursorKind.CHARACTER_LITERAL, CursorKind.STRING_LITERAL]:  # noqa
1✔
419
            # clean prefix
420
            value = re.sub(r'''^(L|u8|u|U)(R|"|')''', r'\2', value)
1✔
421
            # R for raw strings
422
            # we need to remove the raw-char-sequence prefix,suffix
423
            if value[0] == 'R':
1✔
424
                s = value[1:]
1✔
425
                # if there is no '(' in the 17 first char, its not valid
426
                offset = s[:17].index('(')
1✔
427
                delimiter = s[1:offset] # we skip the "
1✔
428
                value = s[offset + 1:-offset - 1]
1✔
429
                return value
1✔
430
            # we strip string delimiters
431
            return value[1:-1]
1✔
432
        elif cursor.kind == CursorKind.MACRO_INSTANTIATION:  # noqa
1✔
433
            # prefix = value[:3].split('"')[0]
434
            return value
×
435
        elif cursor.kind == CursorKind.MACRO_DEFINITION:  # noqa
1✔
436
            c = value[-1]
1✔
437
            if c in ['"', "'"]:
1✔
438
                value = re.sub('''^L%s''' % c , c, value)
1✔
439
            else:
440
                # unsigned int / long int / unsigned long int / long long int / unsigned long long int
441
                # this works and doesn't eat STRING values because no '"' is before $ in the regexp.
442
                # FIXME combinaisons of u/U, l/L, ll/LL, and combined, plus z/Z combined with u/U
443
                value = re.sub("(u|U|l|L|ul|UL|ll|LL|ull|ULL|z|Z|zu|ZU)$", "", value)
1✔
444
            return value
1✔
445
        else:
446
            pass
×
447
        return value
×
448

449
    @log_entity
1✔
450
    def _literal_handling(self, cursor):
1✔
451
        """Parse all literal associated with this cursor.
452

453
        Literal handling is usually useful only for initialization values.
454

455
        We can't use a shortcut by getting tokens
456
            # init_value = ' '.join([t.spelling for t in children[0].get_tokens()
457
            # if t.spelling != ';'])
458
        because some literal might need cleaning."""
459
        # FIXME #77, internal integer literal like __clang_major__ are not working here.
460
        # tokens == [] , because ??? clang problem ? so there is no spelling available.
461
        tokens = list(cursor.get_tokens())
1✔
462
        if cursor.kind == CursorKind.INTEGER_LITERAL and len(tokens) == 0:
1✔
463
            log.warning("INTEGER_LITERAL - clang provides no value - bug #77")
1✔
464
            # https://stackoverflow.com/questions/10692015/libclang-get-primitive-value
465
            # cursor.data[1] ?
466
            # import clang
467
            # # clang.cindex.conf.lib.clang_Cursor_Evaluate.restype = clang.cindex.conf.lib.CXEvalResult
468
            # evaluated = clang.cindex.conf.lib.clang_Cursor_Evaluate(cursor)
469
            # value = clang.cindex.conf.lib.clang_EvalResult_getAsInt(evaluated)
470
            # clang.cindex.conf.lib.clang_EvalResult_dispose(evaluated)
471

472
        log.debug('literal has %d tokens.[ %s ]', len(tokens), ' '.join([str(t.spelling) for t in tokens]))
1✔
473
        if len(tokens) == 1 and cursor.kind == CursorKind.STRING_LITERAL:  # noqa
1✔
474
            # use a shortcut that works for unicode
475
            value = tokens[0].spelling
1✔
476
            value = self._clean_string_literal(cursor, value)
1✔
477
            return value
1✔
478
        elif cursor.kind == CursorKind.STRING_LITERAL:  # noqa
1✔
479
            # use a shortcut - does not work on unicode var_decl
480
            value = cursor.displayname
1✔
481
            value = self._clean_string_literal(cursor, value)
1✔
482
            return value
1✔
483
        final_value = []
1✔
484
        # code.interact(local=locals())
485
        log.debug('cursor.type:%s', cursor.type.kind.name)
1✔
486
        for i, token in enumerate(tokens):
1✔
487
            value = token.spelling
1✔
488
            log.debug('token:%s tk.kd:%11s tk.cursor.kd:%15s cursor.kd:%15s',
1✔
489
                      token.spelling, token.kind.name, token.cursor.kind.name,
490
                      cursor.kind.name)
491
            # Punctuation is probably not part of the init_value,
492
            # but only in specific case: ';' endl, or part of list_expr
493
            if (token.kind == TokenKind.PUNCTUATION and  # noqa
1✔
494
                    (token.cursor.kind == CursorKind.INVALID_FILE or  # noqa
495
                             token.cursor.kind == CursorKind.INIT_LIST_EXPR)):  # noqa
496
                log.debug('IGNORE token %s', value)
×
497
                continue
×
498
            elif token.kind == TokenKind.COMMENT:  # noqa
1✔
499
                log.debug('Ignore comment %s', value)
×
500
                continue
×
501
            # elif token.cursor.kind == CursorKind.VAR_DECL:
502
            elif token.location not in cursor.extent:
1✔
503
                # log.debug('FIXME BUG: token.location not in cursor.extent %s', value)
504
                # 2021 clang 11, this seems fixed ?
505
                # there is most probably a BUG in clang or python-clang
506
                # when on #define with no value, a token is taken from
507
                # next line. Which break stuff.
508
                # example:
509
                #   #define A
510
                #   extern int i;
511
                # // this will give "extern" the last token of Macro("A")
512
                # Lexer is choking ?
513
                # FIXME BUG: token.location not in cursor.extent
514
                # code.interact(local=locals())
515
                continue
×
516
            # Cleanup specific c-lang or c++ prefix/suffix for POD types.
517
            if token.cursor.kind == CursorKind.INTEGER_LITERAL:  # noqa
1✔
518
                # strip type suffix for constants
519
                value = value.replace('L', '').replace('U', '')
1✔
520
                value = value.replace('l', '').replace('u', '')
1✔
521
                if value[:2] == '0x' or value[:2] == '0X':
1✔
522
                    value = '0x%s' % value[2:]  # "int(%s,16)"%(value)
1✔
523
                else:
524
                    value = int(value)
1✔
525
            elif token.cursor.kind == CursorKind.FLOATING_LITERAL:  # noqa
1✔
526
                # strip type suffix for constants
527
                value = value.replace('f', '').replace('F', '')
1✔
528
                value = float(value)
1✔
529
            elif (token.cursor.kind == CursorKind.CHARACTER_LITERAL or  # noqa
1✔
530
                          token.cursor.kind == CursorKind.STRING_LITERAL):  # noqa
531
                value = self._clean_string_literal(token.cursor, value)
1✔
532
            elif token.cursor.kind == CursorKind.MACRO_INSTANTIATION:  # noqa
1✔
533
                # get the macro value
534
                value = self.get_registered(value).body
×
535
                # already cleaned value = self._clean_string_literal(token.cursor, value)
536
            elif token.cursor.kind == CursorKind.MACRO_DEFINITION:  # noqa
1✔
537
                tk = token.kind
1✔
538
                if i == 0:
1✔
539
                    # ignore, macro name
540
                    pass
1✔
541
                elif token.kind == TokenKind.LITERAL:  # noqa
1✔
542
                    # and just clean it
543
                    value = self._clean_string_literal(token.cursor, value)
1✔
544
                elif token.kind == TokenKind.IDENTIFIER:  # noqa
1✔
545
                    # log.debug("Ignored MACRO_DEFINITION token identifier : %s", value)
546
                    # Identifier in Macro... Not sure what to do with that.
547
                    if self.is_registered(value):
1✔
548
                        # FIXME: if Macro is not a simple value replace, it should not be registered in the first place
549
                        # parse that, try to see if there is another Macro in there.
550
                        value = self.get_registered(value).body
1✔
551
                        log.debug("Found MACRO_DEFINITION token identifier : %s", value)
1✔
552
                    else:
553
                        value = typedesc.UndefinedIdentifier(value)
1✔
554
                        log.debug("Undefined MACRO_DEFINITION token identifier : %s", value)
1✔
555
                    pass
1✔
556
                elif token.kind == TokenKind.KEYWORD:  # noqa
1✔
557
                    log.debug("Got a MACRO_DEFINITION referencing a KEYWORD token.kind: %s", token.kind.name)
1✔
558
                    value = typedesc.UndefinedIdentifier(value)
1✔
559
                elif token.kind in [TokenKind.COMMENT, TokenKind.PUNCTUATION]:  # noqa
1✔
560
                    # log.debug("Ignored MACRO_DEFINITION token.kind: %s", token.kind.name)
561
                    pass
1✔
562

563
            # add token
564
            if value is not None:
1✔
565
                final_value.append(value)
1✔
566
        # return the EXPR
567
        # code.interact(local=locals())
568
        # FIXME, that will break. We need constant type return
569
        if len(final_value) == 1:
1✔
570
            return final_value[0]
1✔
571
        # Macro definition of a string using multiple macro
572
        if isinstance(final_value, list):
1✔
573
            if cursor.kind == CursorKind.STRING_LITERAL:  # noqa
1✔
574
                final_value = ''.join(final_value)
×
575
        log.debug('_literal_handling final_value: %s', final_value)
1✔
576
        return final_value
1✔
577

578
    INTEGER_LITERAL = _literal_handling
1✔
579
    FLOATING_LITERAL = _literal_handling
1✔
580
    IMAGINARY_LITERAL = _literal_handling
1✔
581
    STRING_LITERAL = _literal_handling
1✔
582
    CHARACTER_LITERAL = _literal_handling
1✔
583

584
    @log_entity
1✔
585
    def _operator_handling(self, cursor):
1✔
586
        """Returns a string with the literal that are part of the operation."""
587
        values = self._literal_handling(cursor)
1✔
588
        retval = ''.join([str(val) for val in values])
1✔
589
        log.debug('cursor.type.kind:%s', cursor.type.kind.name)
1✔
590
        if cursor.kind == CursorKind.UNARY_OPERATOR:  # noqa
1✔
591
            if cursor.type.kind in [TypeKind.INT, TypeKind.LONG]:  # noqa
1✔
592
                if '0x' in retval:
1✔
593
                    retval = int(retval, 16)
1✔
594
                else:
595
                    try:
1✔
596
                        retval = int(retval)
1✔
597
                    except ValueError:
1✔
598
                        # fall back on pass through
599
                        pass
1✔
600
            elif cursor.type.kind in [TypeKind.FLOAT, TypeKind.DOUBLE]:  # noqa
1✔
601
                retval = float(retval)
1✔
602
        # Things we do not want to do:
603
        # elif cursor.kind == CursorKind.BINARY_OPERATOR:
604
        #     # cursor.kind == binary_operator, then need to make some additions
605
        #     retval = eval(retval)
606

607
        return retval
1✔
608

609
    UNARY_OPERATOR = _operator_handling
1✔
610
    BINARY_OPERATOR = _operator_handling
1✔
611

612
    @log_entity
1✔
613
    def STRUCT_DECL(self, cursor, num=None):
1✔
614
        """
615
        Handles Structure declaration.
616
        Its a wrapper to _record_decl.
617
        """
618
        return self._record_decl(cursor, typedesc.Structure, num)
1✔
619

620
    @log_entity
1✔
621
    def UNION_DECL(self, cursor, num=None):
1✔
622
        """
623
        Handles Union declaration.
624
        Its a wrapper to _record_decl.
625
        """
626
        return self._record_decl(cursor, typedesc.Union, num)
1✔
627

628
    def _record_decl(self, cursor, _output_type, num=None):
1✔
629
        """
630
        Handles record type declaration.
631
        Structure, Union...
632
        """
633
        name = self.get_unique_name(cursor)
1✔
634
        # FIXME, handling anonymous field by adding a child id.
635
        if num is not None:
1✔
636
            name = "%s_%d", name, num
×
637
        # TODO unittest: try redefinition.
638
        # Find if a record definition was already parsed and registered
639
        if (self.is_registered(name) and
1✔
640
                    self.get_registered(name).members is not None):
641
            log.debug(
1✔
642
                '_record_decl: %s is already registered with members',
643
                name)
644
            return self.get_registered(name)
1✔
645
        # CPP bases
646
        bases = []
1✔
647
        for c in cursor.get_children():
1✔
648
            if c.kind == CursorKind.CXX_BASE_SPECIFIER:  # noqa
1✔
649
                bases.append(self.get_registered(self.get_unique_name(c)))
1✔
650
                log.debug("got base class %s", c.displayname)
1✔
651
        size = cursor.type.get_size()
1✔
652
        align = cursor.type.get_align()
1✔
653
        if size == -2: #
1✔
654
            # CXTypeLayoutError_Incomplete = -2
655
            # produce an empty structure declaration
656
            size = align = 0
1✔
657
            log.debug('_record_decl: name: %s CXTypeLayoutError_Incomplete', name)
1✔
658
            obj = _output_type(name, align, None, bases, size, packed=False)
1✔
659
            self.set_location(obj, cursor)
1✔
660
            self.set_comment(obj, cursor)
1✔
661
            return self.register(name, obj)
1✔
662

663
        elif size < 0 or align < 0:
1✔
664
            # CXTypeLayoutError_Invalid = -1,
665
            # CXTypeLayoutError_Dependent = -3,
666
            # CXTypeLayoutError_NotConstantSize = -4,
667
            # CXTypeLayoutError_InvalidFieldName = -5
668
            errs = dict([(-1, "Invalid"), (-3, "Dependent"),
×
669
                         (-4, "NotConstantSize"), (-5, "InvalidFieldName")])
670
            loc = "%s:%s" % (cursor.location.file, cursor.location.line)
×
671
            log.error('Structure %s is %s %s align:%d size:%d',
×
672
                      name, errs[size], loc, align, size)
673
            raise InvalidDefinitionError('Structure %s is %s %s align:%d size:%d',
×
674
                                         name, errs[size], loc, align, size)
675
        else:
676
            log.debug('_record_decl: name: %s size:%d', name, size)
1✔
677
        # Declaration vs Definition point
678
        # when a struct decl happen before the definition, we have no members
679
        # in the first declaration instance.
680
        obj = None
1✔
681
        if not self.is_registered(name):
1✔
682
            if not cursor.is_definition():
1✔
683
                # just save the spot, don't look at members == None
684
                log.debug('cursor %s is not on a definition', name)
1✔
685
                obj = _output_type(name, align, None, bases, size, packed=False)
1✔
686
                return self.register(name, obj)
1✔
687
            else:
688
                log.debug('cursor %s is a definition', name)
1✔
689
                # save the type in the registry. Useful for not looping in case of
690
                # members with forward references
691
                obj = _output_type(name, align, None, bases, size, packed=False)
1✔
692
                self.register(name, obj)
1✔
693
                self.set_location(obj, cursor)
1✔
694
                self.set_comment(obj, cursor)
1✔
695
                declared_instance = True
1✔
696
        else:
697
            obj = self.get_registered(name)
1✔
698
            declared_instance = False
1✔
699
        # capture members declaration
700
        members = []
1✔
701
        # Go and recurse through fields
702
        fields = list(cursor.type.get_fields())
1✔
703
        decl_f = [f.type.get_declaration() for f in fields]
1✔
704
        log.debug('Fields: %s',
1✔
705
                  str(['%s/%s' % (f.kind.name, f.spelling) for f in fields]))
706
        for field in fields:
1✔
707
            log.debug('creating FIELD_DECL for %s/%s', field.kind.name, field.spelling)
1✔
708
            members.append(self.FIELD_DECL(field))
1✔
709
        # FIXME BUG clang: anonymous structure field with only one anonymous field
710
        # is not a FIELD_DECL. does not appear in get_fields() !!!
711
        #
712
        # check for other stuff
713
        for child in cursor.get_children():
1✔
714
            if child in fields:
1✔
715
                continue
1✔
716
            elif child in decl_f:
1✔
717
                continue
1✔
718
            elif child.kind == CursorKind.PACKED_ATTR:  # noqa
1✔
719
                obj.packed = True
1✔
720
                log.debug('PACKED record')
1✔
721
                continue  # dont mess with field calculations
1✔
722
            else:  # could be others.... struct_decl, etc...
723
                log.debug(
1✔
724
                    'Unhandled field %s in record %s',
725
                    child.kind, name)
726
                continue
1✔
727
        log.debug('_record_decl: %d members', len(members))
1✔
728
        # by now, the type is registered.
729
        if not declared_instance:
1✔
730
            log.debug('_record_decl: %s was previously registered', name)
1✔
731
        obj = self.get_registered(name)
1✔
732
        obj.members = members
1✔
733
        # obj.packed = packed
734
        # final fixup
735
        self._fixup_record(obj)
1✔
736
        return obj
1✔
737

738
    def _fixup_record_bitfields_type(self, s):
1✔
739
        """Fix the bitfield packing issue for python ctypes, by changing the
740
        bitfield type, and respecting compiler alignement rules.
741

742
        This method should be called AFTER padding to have a perfect continuous
743
        layout.
744

745
        There is one very special case:
746
            struct bytes3 {
747
                unsigned int b1:23; // 0-23
748
                // 1 bit padding
749
                char a2; // 24-32
750
            };
751

752
        where we would need to actually put a2 in the int32 bitfield.
753

754
        We also need to change the member type to the smallest type possible
755
        that can contains the number of bits.
756
        Otherwise ctypes has strange bitfield rules packing stuff to the biggest
757
        type possible.
758

759
        ** but at the same time, if a bitfield member is from type x, we need to
760
        respect that
761
        """
762
        # phase 1, make bitfield, relying upon padding.
763
        bitfields = []
1✔
764
        bitfield_members = []
1✔
765
        current_bits = 0
1✔
766
        for m in s.members:
1✔
767
            if m.is_bitfield:
1✔
768
                bitfield_members.append(m)
1✔
769
                if m.is_padding:
1✔
770
                    # compiler says this ends the bitfield
771
                    size = current_bits
×
772
                    bitfields.append((size, bitfield_members))
×
773
                    bitfield_members = []
×
774
                    current_bits = 0
×
775
                else:
776
                    # size of padding is not included
777
                    current_bits += m.bits
1✔
778
            elif len(bitfield_members) == 0:
1✔
779
                # no opened bitfield
780
                continue
1✔
781
            else:
782
                # we reach the end of the bitfield. Make calculations.
783
                size = current_bits
1✔
784
                bitfields.append((size, bitfield_members))
1✔
785
                bitfield_members = []
1✔
786
                current_bits = 0
1✔
787
        if current_bits != 0:
1✔
788
            size = current_bits
1✔
789
            bitfields.append((size, bitfield_members))
1✔
790

791
        # compilers tend to reduce the size of the bitfield
792
        # to the bf_size
793
        # set the proper type name for the bitfield.
794
        for bf_size, members in bitfields:
1✔
795
            name = members[0].type.name
1✔
796
            pad_bits = 0
1✔
797
            if bf_size <= 8:  # use 1 byte - type = char
1✔
798
                # prep the padding bitfield size
799
                pad_bits = 8 - bf_size
1✔
800
            elif bf_size <= 16:  # use 2 byte
1✔
801
                pad_bits = 16 - bf_size
1✔
802
            elif bf_size <= 32:  # use 2 byte
1✔
803
                pad_bits = 32 - bf_size
1✔
804
            elif bf_size <= 64:  # use 2 byte
1✔
805
                name = 'c_uint64'  # also the 3 bytes + char thing
1✔
806
                pad_bits = 64 - bf_size
1✔
807
            else:
808
                name = 'c_uint64'
1✔
809
                pad_bits = bf_size % 64 - bf_size
1✔
810
            # change the type to harmonise the bitfield
811
            log.debug('_fixup_record_bitfield_size: fix type to %s', name)
1✔
812
            # set the whole bitfield to the appropriate type size.
813
            for m in members:
1✔
814
                m.type.name = name
1✔
815
                if m.is_padding:
1✔
816
                    # this is the last field.
817
                    # reduce the size of this padding field to the
818
                    m.bits = pad_bits
×
819
            # and remove padding if the size is 0
820
            if members[-1].is_padding and members[-1].bits == 0:
1✔
821
                s.members.remove(members[-1])
×
822

823
        # phase 2 - integrate the special 3 Bytes + char fix
824
        for bf_size, members in bitfields:
1✔
825
            if True or bf_size == 24:
1✔
826
                # we need to check for a 3bytes + char corner case
827
                m = members[-1]
1✔
828
                i = s.members.index(m)
1✔
829
                if len(s.members) > i + 1:
1✔
830
                    # has to exists, no arch is aligned on 24 bits.
831
                    next_member = s.members[i + 1]
1✔
832
                    if next_member.bits == 8:
1✔
833
                        # next_member field is a char.
834
                        # it will be aggregated in a 32 bits space
835
                        # we need to make it a member of 32bit bitfield
836
                        next_member.is_bitfield = True
1✔
837
                        next_member.comment = "Promoted to bitfield member and type (was char)"
1✔
838
                        next_member.type = m.type
1✔
839
                        log.info("%s.%s promoted to bitfield member and type", s.name, next_member.name)
1✔
840
                        continue
1✔
841
        #
842
        return
1✔
843

844
    def _fixup_record(self, s):
1✔
845
        """Fixup padding on a record"""
846
        log.debug('FIXUP_STRUCT: %s %d bits', s.name, s.size * 8)
1✔
847
        if s.members is None:
1✔
848
            log.debug('FIXUP_STRUCT: no members')
×
849
            s.members = []
×
850
            return
×
851
        if s.size == 0:
1✔
852
            log.debug('FIXUP_STRUCT: struct has size %d', s.size)
1✔
853
            return
1✔
854
        # try to fix bitfields without padding first
855
        self._fixup_record_bitfields_type(s)
1✔
856
        # No need to lookup members in a global var.
857
        # Just fix the padding
858
        members = []
1✔
859
        member = None
1✔
860
        offset = 0
1✔
861
        padding_nb = 0
1✔
862
        member = None
1✔
863
        prev_member = None
1✔
864
        # create padding fields
865
        # DEBUG FIXME: why are s.members already typedesc objet ?
866
        # fields = self.fields[s.name]
867
        for m in s.members:  # s.members are strings - NOT
1✔
868
            # we need to check total size of bitfield, so to choose the right
869
            # bitfield type
870
            member = m
1✔
871
            log.debug('Fixup_struct: Member:%s offsetbits:%d->%d expecting offset:%d',
1✔
872
                      member.name, member.offset, member.offset + member.bits, offset)
873
            if member.offset < 0:
1✔
874
                # FIXME INCOMPLETEARRAY (clang bindings?)
875
                # All fields have offset == -2. No padding will be done.
876
                # But the fields are ordered and code will be produces with typed info.
877
                # so in most cases, it will work. if there is a structure with incompletearray
878
                # and padding or alignement issue, it will produce wrong results
879
                # just exit
880
                return
×
881
            if member.offset > offset:
1✔
882
                # create padding
883
                length = member.offset - offset
1✔
884
                log.debug(
1✔
885
                    'Fixup_struct: create padding for %d bits %d bytes',
886
                    length, length // 8)
887
                padding_nb = self._make_padding(
1✔
888
                    members,
889
                    padding_nb,
890
                    offset,
891
                    length,
892
                    prev_member)
893
            if member.type is None:
1✔
894
                log.error('FIXUP_STRUCT: %s.type is None', member.name)
×
895
            members.append(member)
1✔
896
            offset = member.offset + member.bits
1✔
897
            prev_member = member
1✔
898
        # tail padding if necessary
899
        if s.size * 8 != offset:
1✔
900
            length = s.size * 8 - offset
1✔
901
            log.debug(
1✔
902
                'Fixup_struct: s:%d create tail padding for %d bits %d bytes',
903
                s.size, length, length // 8)
904
            padding_nb = self._make_padding(
1✔
905
                members,
906
                padding_nb,
907
                offset,
908
                length,
909
                prev_member)
910
        if len(members) > 0:
1✔
911
            offset = members[-1].offset + members[-1].bits
1✔
912
        # go
913
        s.members = members
1✔
914
        log.debug("FIXUP_STRUCT: size:%d offset:%d", s.size * 8, offset)
1✔
915
        # if member and not member.is_bitfield:
916
        ## self._fixup_record_bitfields_type(s)
917
        # , assert that the last field stop at the size limit
918
        assert offset == s.size * 8
1✔
919
        return
1✔
920

921
    _fixup_Structure = _fixup_record
1✔
922
    _fixup_Union = _fixup_record
1✔
923

924
    def _make_padding(
1✔
925
            self, members, padding_nb, offset, length, prev_member=None):
926
        """Make padding Fields for a specifed size."""
927
        name = 'PADDING_%d' % padding_nb
1✔
928
        padding_nb += 1
1✔
929
        log.debug("_make_padding: for %d bits", length)
1✔
930
        if (length % 8) != 0 or (prev_member is not None and prev_member.is_bitfield):
1✔
931
            if length > 32:
1✔
932
                typename = "c_uint64"
1✔
933
            elif length > 16:
1✔
934
                typename = "c_uint32"
1✔
935
            elif length > 8:
1✔
936
                typename = "c_uint16"
1✔
937
            else:
938
                typename = "c_uint8"
1✔
939
            padding = typedesc.Field(name,
1✔
940
                                     typedesc.FundamentalType(typename, 1, 1),
941
                                     offset, length, is_bitfield=True, is_padding=True)
942
            members.append(padding)
1✔
943
            return padding_nb
1✔
944
        elif length > 8:
1✔
945
            pad_bytes = length // 8
1✔
946
            padding = typedesc.Field(name,
1✔
947
                                     typedesc.ArrayType(
948
                                         typedesc.FundamentalType(
949
                                             self.get_ctypes_name(TypeKind.CHAR_U), length, 1),  # noqa
950
                                         pad_bytes),
951
                                     offset, length, is_padding=True)
952
            members.append(padding)
1✔
953
            return padding_nb
1✔
954
        # simple char padding
955
        padding = typedesc.Field(name,
1✔
956
                                 typedesc.FundamentalType(
957
                                     self.get_ctypes_name(
958
                                         TypeKind.CHAR_U),  # noqa
959
                                     1,
960
                                     1),
961
                                 offset, length, is_padding=True)
962
        members.append(padding)
1✔
963
        return padding_nb
1✔
964

965
    # FIXME
966
    CLASS_DECL = STRUCT_DECL
1✔
967
    _fixup_Class = _fixup_record
1✔
968

969
    # @log_entity DEBUG
970
    def FIELD_DECL(self, cursor):
1✔
971
        """
972
        Handles Field declarations.
973
        Some specific treatment for a bitfield.
974
        """
975
        # name, type
976
        parent = cursor.semantic_parent
1✔
977
        # field name:
978
        # either its cursor.spelling or it is an anonymous bitfield
979
        # we do NOT rely on get_unique_name for a bitfield name.
980
        # Anonymous Field:
981
        #    We have to create a name
982
        #    it will be the indice of the field (_0,_1,...)
983
        # offset of field:
984
        #    we will need it late. get the offset of the field in the record
985
        # Note: cursor.is_anonymous seems to be unreliable/inconsistent across
986
        # libclang versions, and we will consider the field as anonymous if
987
        # cursor.spelling is empty
988
        # but at least with clang-17.. anonymous fields have a name "type (anonymous at ..)"
989
        name = cursor.spelling
1✔
990
        offset = parent.type.get_offset(name)
1✔
991
        if "(anonymous" in name:
1✔
992
            name = ""
1✔
993
        if not name and cursor.is_anonymous() and not cursor.is_bitfield():
1✔
994
            # anonymous type, that is not a bitfield field case:
995
            offset = cursor.get_field_offsetof()
1✔
996
            # name = self.get_unique_name(cursor)
997
            # we want to keep name empty if the field is unnamed.
998
        elif not name:
1✔
999
            # anonymous bitfield case:
1000
            # get offset by iterating all fields of parent
1001
            # corner case for anonymous fields
1002
            # if offset == -5: use field.get_offset_of()
1003
            fieldnum = 0
1✔
1004
            offset = cursor.get_field_offsetof()
1✔
1005
            for i, _f in enumerate(parent.type.get_fields()):
1✔
1006
                if _f == cursor:
1✔
1007
                    fieldnum = i
1✔
1008
                    break
1✔
1009
            # make a name
1010
            if fieldnum == -1:
1✔
1011
                raise ValueError("Anonymous field was not found in get_fields()")
×
1012
            name = "_%d" % fieldnum
1✔
1013
            log.debug("FIELD_DECL: anonymous field renamed to %s", name)
1✔
1014
        # some debug
1015
        if offset < 0:
1✔
1016
            log.error('FIELD_DECL: BAD RECORD, Bad offset: %d for %s', offset, name)
×
1017
            # incomplete record definition, gives us an error here on fields.
1018
            # BUG clang bindings ?
1019
        # FIXME if c++ class ?
1020
        log.debug('FIELD_DECL: field offset is %d', offset)
1✔
1021

1022
        # bitfield checks
1023
        bits = None
1✔
1024
        if cursor.is_bitfield():
1✔
1025
            log.debug('FIELD_DECL: field is part of a bitfield')
1✔
1026
            bits = cursor.get_bitfield_width()
1✔
1027
        else:
1028
            bits = cursor.type.get_size() * 8
1✔
1029
            if bits < 0:
1✔
1030
                log.warning('Bad source code, bitsize == %d <0 on %s', bits, name)
1✔
1031
                bits = 0
1✔
1032
        log.debug('FIELD_DECL: field is %d bits', bits)
1✔
1033
        # try to get a representation of the type
1034
        # _canonical_type = cursor.type.get_canonical()
1035
        # t-t-t-t-
1036
        _type = None
1✔
1037
        _canonical_type = cursor.type.get_canonical()
1✔
1038
        _decl = cursor.type.get_declaration()
1✔
1039
        if (self.is_array_type(_canonical_type) or
1✔
1040
                self.is_fundamental_type(_canonical_type) or
1041
                self.is_pointer_type(_canonical_type)):
1042
            _type = self.parse_cursor_type(_canonical_type)
1✔
1043
        else:
1044
            children = list(cursor.get_children())
1✔
1045
            log.debug('FIELD_DECL: we now look for the declaration name.'
1✔
1046
                      'kind %s', _decl.kind)
1047
            if len(children) > 0 and _decl.kind == CursorKind.NO_DECL_FOUND:  # noqa
1✔
1048
                # constantarray of typedef of pointer , and other cases ?
1049
                _decl_name = self.get_unique_name(
×
1050
                    list(
1051
                        cursor.get_children())[0])
1052
            else:
1053
                # pass a field name to get a better name for an anonymous record, that is a named field
1054
                _decl_name = self.get_unique_name(_decl, field_name=name)
1✔
1055
            log.debug('FIELD_DECL: the declaration name %s', _decl_name)
1✔
1056
            # rename anonymous field type name
1057
            # 2015-06-26 handled in get_name
1058
            # if cursor.is_anonymous():
1059
            #    _decl_name += name
1060
            #    log.debug('FIELD_DECL: IS_ANONYMOUS the declaration name %s',_decl_name)
1061
            if self.is_registered(_decl_name):
1✔
1062
                log.debug(
1✔
1063
                    'FIELD_DECL: used type from cache: %s',
1064
                    _decl_name)
1065
                _type = self.get_registered(_decl_name)
1✔
1066
                # then we shortcut
1067
            else:
1068
                # is it always the case ?
1069
                log.debug("FIELD_DECL: name:'%s'", _decl_name)
1✔
1070
                log.debug("FIELD_DECL: %s: nb children:%s", cursor.type.kind,
1✔
1071
                          len(children))
1072
                # recurse into the right function
1073
                _type = self.parse_cursor_type(_canonical_type)
1✔
1074
                if _type is None:
1✔
1075
                    log.warning("Field %s is an %s type - ignoring field type",
×
1076
                                name, _canonical_type.kind.name)
1077
                    return None
×
1078
        if cursor.is_anonymous():
1✔
1079
            # we have to unregister the _type and register an alternate named
1080
            # type.
1081
            self.parser.remove_registered(_type.name)
1✔
1082
            _type.name = _decl_name
1✔
1083
            self.register(_decl_name, _type)
1✔
1084
        return typedesc.Field(name, _type, offset, bits,
1✔
1085
                              is_bitfield=cursor.is_bitfield(),
1086
                              is_anonymous=cursor.is_anonymous())
1087

1088
    #############################
1089
    # PREPROCESSING
1090

1091
    @log_entity
1✔
1092
    def MACRO_DEFINITION(self, cursor):
1✔
1093
        """
1094
        Parse MACRO_DEFINITION, only present if the TranslationUnit is
1095
        used with TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD.
1096
        By default, macro are not parsed. requires -k m || parser.activate_macros_parsing()
1097
        """
1098
        # macro parsing takes a LOT of time.
1099
        # ignore system macro
1100
        if (not hasattr(cursor, 'location') or cursor.location is None or
1✔
1101
                cursor.location.file is None):
1102
            # keep track of sizes from clang directly
1103
            # but we already did that elsewhere in clangparser.py make_ctypes_convertor
1104
            # if cursor.displayname.startswith('__SIZEOF_'):
1105
            #     typ = cursor.displayname[len('__SIZEOF_'):-2]
1106
            #     self.__sizeof[typ] = list(cursor.get_tokens())[1].spelling
1107
            return False
1✔
1108
        name = self.get_unique_name(cursor)
1✔
1109
        # MACRO_DEFINITION are a list of Tokens
1110
        # .kind = {IDENTIFIER, KEYWORD, LITERAL, PUNCTUATION, COMMENT ? }
1111
        comment = None
1✔
1112
        tokens = self._literal_handling(cursor)
1✔
1113
        # Macro name is tokens[0]
1114
        # get Macro value(s)
1115
        value = True
1✔
1116
        # args should be filled when () are in tokens,
1117
        args = None
1✔
1118
        if isinstance(tokens, list):
1✔
1119
            # TODO, if there is an UndefinedIdentifier, we need to scrap the whole thing to comments.
1120
            # unknowns = [_ for _ in tokens if isinstance(_, typedesc.UndefinedIdentifier)]
1121
            # if len(unknowns) > 0:
1122
            #     value = tokens
1123
            # elif len(tokens) == 2:
1124
            if len(tokens) == 2:
1✔
1125
                # #define key value
1126
                value = tokens[1]
1✔
1127
            elif len(tokens) == 3 and tokens[1] == '-':
1✔
1128
                value = ''.join(tokens[1:])
1✔
1129
            elif tokens[1] == '(':
1✔
1130
                # #107, differentiate between function-like macro and expression in ()
1131
                # valid tokens for us are are '()[0-9],.e' and terminating LluU
1132
                if any(filter(lambda x: isinstance(x, typedesc.UndefinedIdentifier), tokens)):
1✔
1133
                    # function macro or an expression.
1134
                    str_tokens = [str(_) for _ in tokens[1:tokens.index(')')+1]]
1✔
1135
                    args = ''.join(str_tokens).replace(',', ', ')
1✔
1136
                    str_tokens = [str(_) for _ in tokens[tokens.index(')')+1:]]
1✔
1137
                    value = ''.join(str_tokens)
1✔
1138
                else:
1139
                    value = ''.join((str(_) for _ in tokens[1:tokens.index(')') + 1]))
1✔
1140
            elif len(tokens) > 2:
1✔
1141
                # #define key a b c
1142
                value = list(tokens[1:])
1✔
1143
            else:
1144
                # FIXME no reach ?!
1145
                # just merge the list of tokens
1146
                value = ' '.join(tokens[1:])
×
1147
        elif isinstance(tokens, str):
1✔
1148
            # #define only
1149
            value = True
1✔
1150
        # macro comment maybe in tokens. Not in cursor.raw_comment
1151
        for t in cursor.get_tokens():
1✔
1152
            if t.kind == TokenKind.COMMENT:  ## noqa
1✔
1153
                comment = t.spelling
×
1154
        # special case. internal __null or __thread
1155
        # FIXME, there are probable a lot of others.
1156
        # why not Cursor.kind GNU_NULL_EXPR child instead of a token ?
1157
        if name in ['NULL', '__thread'] or value in ['__null', '__thread']:
1✔
1158
            value = None
1✔
1159
        log.debug('MACRO: #define %s%s %s', name, args or '', value)
1✔
1160
        obj = typedesc.Macro(name, args, value)
1✔
1161
        try:
1✔
1162
            self.register(name, obj)
1✔
1163
        except DuplicateDefinitionException:
1✔
1164
            log.info('Redefinition of %s %s->%s', name, self.parser.all[name].args, value)
1✔
1165
            # HACK
1166
            self.parser.all[name] = obj
1✔
1167
        self.set_location(obj, cursor)
1✔
1168
        # set the comment in the obj
1169
        obj.comment = comment
1✔
1170
        return True
1✔
1171

1172
    @log_entity
1✔
1173
    def MACRO_INSTANTIATION(self, cursor):
1✔
1174
        """We could use this to count instantiations
1175
        so we now, if we need to generate python code or comment for this macro ? """
1176
        log.debug('cursor.spelling: %s', cursor.spelling)
1✔
1177
        # log.debug('cursor.kind: %s', cursor.kind.name)
1178
        # log.debug('cursor.type.kind: %s', cursor.type.kind.name)
1179
        # # no children ?
1180
        # for child in cursor.get_children():
1181
        #     log.debug('child.spelling: %s', child.spelling)
1182
        #     log.debug('child.kind: %s', child.kind.name)
1183
        #     log.debug('child.type.kind: %s', child.type.kind.name)
1184
        #
1185
        # for token in cursor.get_tokens():
1186
        #     log.debug('token.spelling: %s', token.spelling)
1187
        #     log.debug('token.kind: %s', token.kind.name)
1188
        #     #log.debug('token.type.kind: %s', token.type.kind.name)
1189

1190
        # ret.append(self.parse_cursor(child))
1191
        # log.debug('cursor.type:%s', cursor.type.kind.name)
1192
        # self.set_location(obj, cursor)
1193
        # set the comment in the obj
1194
        # obj.comment = comment
1195
        return True
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