1"""
2cppcheckdata
3
4This is a Python module that helps you access Cppcheck dump data.
5
6License: No restrictions, use this as you need.
7"""
8
9import argparse
10import json
11import sys
12
13from xml.etree import ElementTree
14from fnmatch import fnmatch
15
16EXIT_CODE = 0
17
18current_dumpfile_suppressions = []
19
20def _load_location(location, element):
21    """Load location from element/dict"""
22    location.file = element.get('file')
23    line = element.get('line')
24    if line is None:
25        line = element.get('linenr')
26    if line is None:
27        line = '0'
28    location.linenr = int(line)
29    location.column = int(element.get('column', '0'))
30
31
32class Location:
33    """Utility location class"""
34    file = None
35    linenr = None
36    column = None
37    def __init__(self, element):
38        _load_location(self, element)
39
40
41class Directive:
42    """
43    Directive class. Contains information about each preprocessor directive in the source code.
44
45    Attributes:
46        str      The directive line, with all C or C++ comments removed
47        file     Name of (possibly included) file where directive is defined
48        linenr   Line number in (possibly included) file where directive is defined
49
50    To iterate through all directives use such code:
51    @code
52    data = cppcheckdata.parsedump(...)
53    for cfg in data.configurations:
54      for directive in cfg.directives:
55        print(directive.str)
56    @endcode
57    """
58
59    str = None
60    file = None
61    linenr = None
62    column = None
63
64    def __init__(self, element):
65        self.str = element.get('str')
66        _load_location(self, element)
67
68    def __repr__(self):
69        attrs = ["str", "file", "linenr"]
70        return "{}({})".format(
71            "Directive",
72            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
73        )
74
75class MacroUsage:
76    """
77    Tracks preprocessor macro usage
78    """
79
80    name = None  # Macro name
81    file = None
82    linenr = None
83    column = None
84    usefile = None
85    uselinenr = None
86    usecolumn = None
87
88    def __init__(self, element):
89        self.name = element.get('name')
90        _load_location(self, element)
91        self.usefile = element.get('usefile')
92        self.useline = element.get('useline')
93        self.usecolumn = element.get('usecolumn')
94
95    def __repr__(self):
96        attrs = ["name", "file", "linenr", "column", "usefile", "useline", "usecolumn"]
97        return "{}({})".format(
98            "MacroUsage",
99            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
100        )
101
102
103class PreprocessorIfCondition:
104    """
105    Information about #if/#elif conditions
106    """
107
108    file = None
109    linenr = None
110    column = None
111    E = None
112    result = None
113
114    def __init__(self, element):
115        _load_location(self, element)
116        self.E = element.get('E')
117        self.result = int(element.get('result'))
118
119    def __repr__(self):
120        attrs = ["file", "linenr", "column", "E", "result"]
121        return "{}({})".format(
122            "PreprocessorIfCondition",
123            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
124        )
125
126
127class ValueType:
128    """
129    ValueType class. Contains (promoted) type information for each node in the AST.
130    """
131
132    type = None
133    sign = None
134    bits = 0
135    constness = 0
136    pointer = 0
137    typeScopeId = None
138    typeScope = None
139    originalTypeName = None
140
141    def __init__(self, element):
142        self.type = element.get('valueType-type')
143        self.sign = element.get('valueType-sign')
144        bits = element.get('valueType-bits')
145        if bits:
146            self.bits = int(bits)
147        self.typeScopeId = element.get('valueType-typeScope')
148        self.originalTypeName = element.get('valueType-originalTypeName')
149        constness = element.get('valueType-constness')
150        if constness:
151            self.constness = int(constness)
152        else:
153            self.constness = 0
154        pointer = element.get('valueType-pointer')
155        if pointer:
156            self.pointer = int(pointer)
157        else:
158            self.pointer = 0
159
160    def __repr__(self):
161        attrs = ["type", "sign", "bits", "typeScopeId", "originalTypeName",
162                 "constness", "pointer"]
163        return "{}({})".format(
164            "ValueType",
165            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
166        )
167
168
169    def setId(self, IdMap):
170        self.typeScope = IdMap[self.typeScopeId]
171
172    def isIntegral(self):
173        return self.type in {'bool', 'char', 'short', 'int', 'long', 'long long'}
174
175    def isFloat(self):
176        return self.type in {'float', 'double', 'long double'}
177
178    def isEnum(self):
179        return self.typeScope and self.typeScope.type == "Enum"
180
181
182class Token:
183    """
184    Token class. Contains information about each token in the source code.
185
186    The CppcheckData.tokenlist is a list of Token items
187
188    C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classToken.html
189
190    Attributes:
191        str                Token string
192        next               Next token in tokenlist. For last token, next is None.
193        previous           Previous token in tokenlist. For first token, previous is None.
194        link               Linked token in tokenlist. Each '(', '[' and '{' are linked to the
195                           corresponding '}', ']' and ')'. For templates, the '<' is linked to
196                           the corresponding '>'.
197        scope              Scope information for this token. See the Scope class.
198        isName             Is this token a symbol name
199        isNumber           Is this token a number, for example 123, 12.34
200        isInt              Is this token a int value such as 1234
201        isFloat            Is this token a float value such as 12.34
202        isString           Is this token a string literal such as "hello"
203        strlen             string length for string literal
204        isChar             Is this token a char literal such as 'x'
205        isOp               Is this token a operator
206        isArithmeticalOp   Is this token a arithmetic operator
207        isAssignmentOp     Is this token a assignment operator
208        isComparisonOp     Is this token a comparison operator
209        isLogicalOp        Is this token a logical operator: && ||
210        isUnsigned         Is this token a unsigned type
211        isSigned           Is this token a signed type
212        isExpandedMacro    Is this token a expanded macro token
213        isSplittedVarDeclComma  Is this a comma changed to semicolon in a splitted variable declaration ('int a,b;' => 'int a; int b;')
214        isSplittedVarDeclEq     Is this a '=' changed to semicolon in a splitted variable declaration ('int a=5;' => 'int a; a=5;')
215        isImplicitInt      Is this token an implicit "int"?
216        varId              varId for token, each variable has a unique non-zero id
217        variable           Variable information for this token. See the Variable class.
218        function           If this token points at a function call, this attribute has the Function
219                           information. See the Function class.
220        values             Possible/Known values of token
221        impossible_values  Impossible values of token
222        valueType          type information
223        typeScope          type scope (token->type()->classScope)
224        astParent          ast parent
225        astOperand1        ast operand1
226        astOperand2        ast operand2
227        file               file name
228        linenr             line number
229        column             column
230
231    To iterate through all tokens use such code:
232    @code
233    data = cppcheckdata.parsedump(...)
234    for cfg in data.configurations:
235      code = ''
236      for token in cfg.tokenlist:
237        code = code + token.str + ' '
238      print(code)
239    @endcode
240    """
241
242    Id = None
243    str = None
244    next = None
245    previous = None
246    linkId = None
247    link = None
248    scopeId = None
249    scope = None
250    isName = False
251    isNumber = False
252    isInt = False
253    isFloat = False
254    isString = False
255    strlen = None
256    isChar = False
257    isOp = False
258    isArithmeticalOp = False
259    isAssignmentOp = False
260    isComparisonOp = False
261    isLogicalOp = False
262    isUnsigned = False
263    isSigned = False
264    isExpandedMacro = False
265    isSplittedVarDeclComma = False
266    isSplittedVarDeclEq = False
267    isImplicitInt = False
268    varId = None
269    variableId = None
270    variable = None
271    functionId = None
272    function = None
273    valuesId = None
274    values = None
275    impossible_values = None
276    valueType = None
277
278    typeScopeId = None
279    typeScope = None
280
281    astParentId = None
282    astParent = None
283    astOperand1Id = None
284    astOperand1 = None
285    astOperand2Id = None
286    astOperand2 = None
287
288    file = None
289    linenr = None
290    column = None
291
292    def __init__(self, element):
293        self.Id = element.get('id')
294        self.str = element.get('str')
295        self.next = None
296        self.previous = None
297        self.scopeId = element.get('scope')
298        self.scope = None
299        type = element.get('type')
300        if type == 'name':
301            self.isName = True
302            if element.get('isUnsigned'):
303                self.isUnsigned = True
304            if element.get('isSigned'):
305                self.isSigned = True
306        elif type == 'number':
307            self.isNumber = True
308            if element.get('isInt'):
309                self.isInt = True
310            elif element.get('isFloat'):
311                self.isFloat = True
312        elif type == 'string':
313            self.isString = True
314            self.strlen = int(element.get('strlen'))
315        elif type == 'char':
316            self.isChar = True
317        elif type == 'op':
318            self.isOp = True
319            if element.get('isArithmeticalOp'):
320                self.isArithmeticalOp = True
321            elif element.get('isAssignmentOp'):
322                self.isAssignmentOp = True
323            elif element.get('isComparisonOp'):
324                self.isComparisonOp = True
325            elif element.get('isLogicalOp'):
326                self.isLogicalOp = True
327        if element.get('isExpandedMacro'):
328            self.isExpandedMacro = True
329        if element.get('isSplittedVarDeclComma'):
330            self.isSplittedVarDeclComma = True
331        if element.get('isSplittedVarDeclEq'):
332            self.isSplittedVarDeclEq = True
333        if element.get('isImplicitInt'):
334            self.isImplicitInt = True
335        self.linkId = element.get('link')
336        self.link = None
337        if element.get('varId'):
338            self.varId = int(element.get('varId'))
339        self.variableId = element.get('variable')
340        self.variable = None
341        self.functionId = element.get('function')
342        self.function = None
343        self.valuesId = element.get('values')
344        self.values = None
345        if element.get('valueType-type'):
346            self.valueType = ValueType(element)
347        else:
348            self.valueType = None
349        self.typeScopeId = element.get('type-scope')
350        self.typeScope = None
351        self.astParentId = element.get('astParent')
352        self.astParent = None
353        self.astOperand1Id = element.get('astOperand1')
354        self.astOperand1 = None
355        self.astOperand2Id = element.get('astOperand2')
356        self.astOperand2 = None
357        _load_location(self, element)
358
359    def __repr__(self):
360        attrs = ["Id", "str", "scopeId", "isName", "isUnsigned", "isSigned",
361                "isNumber", "isInt", "isFloat", "isString", "strlen",
362                "isChar", "isOp", "isArithmeticalOp", "isComparisonOp",
363                "isLogicalOp", "isExpandedMacro", "isSplittedVarDeclComma",
364                "isSplittedVarDeclEq", "isImplicitInt", "linkId", "varId",
365                "variableId", "functionId", "valuesId", "valueType",
366                "typeScopeId", "astParentId", "astOperand1Id", "file",
367                "linenr", "column"]
368        return "{}({})".format(
369            "Token",
370            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
371        )
372
373    def setId(self, IdMap):
374        self.scope = IdMap[self.scopeId]
375        self.link = IdMap[self.linkId]
376        self.variable = IdMap[self.variableId]
377        self.function = IdMap[self.functionId]
378        self.values = []
379        self.impossible_values = []
380        if IdMap[self.valuesId]:
381            for v in IdMap[self.valuesId]:
382                if v.isImpossible():
383                    self.impossible_values.append(v)
384                else:
385                    self.values.append(v)
386                v.setId(IdMap)
387        self.typeScope = IdMap[self.typeScopeId]
388        self.astParent = IdMap[self.astParentId]
389        self.astOperand1 = IdMap[self.astOperand1Id]
390        self.astOperand2 = IdMap[self.astOperand2Id]
391        if self.valueType:
392            self.valueType.setId(IdMap)
393
394    def getValue(self, v):
395        """
396        Get value if it exists
397        Returns None if it doesn't exist
398        """
399
400        if not self.values:
401            return None
402        for value in self.values:
403            if value.intvalue == v:
404                return value
405        return None
406
407    def getKnownIntValue(self):
408        """
409        If token has a known int value then return that.
410        Otherwise returns None
411        """
412        if not self.values:
413            return None
414        for value in self.values:
415            if value.valueKind == 'known':
416                return value.intvalue
417        return None
418
419    def isUnaryOp(self, op):
420        return self.astOperand1 and (self.astOperand2 is None) and self.str == op
421
422    def isBinaryOp(self):
423        return self.astOperand1 and self.astOperand2
424
425class Scope:
426    """
427    Scope. Information about global scope, function scopes, class scopes, inner scopes, etc.
428    C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classScope.html
429
430    Attributes
431        bodyStart      The { Token for this scope
432        bodyEnd        The } Token for this scope
433        className      Name of this scope.
434                       For a function scope, this is the function name;
435                       For a class scope, this is the class name.
436        function       If this scope belongs at a function call, this attribute
437                       has the Function information. See the Function class.
438        type           Type of scope: Global, Function, Class, If, While
439    """
440
441    Id = None
442    bodyStartId = None
443    bodyStart = None
444    bodyEndId = None
445    bodyEnd = None
446    className = None
447    functionId = None
448    function = None
449    nestedInId = None
450    nestedIn = None
451    type = None
452    isExecutable = None
453    varlistId = None
454    varlist = None
455
456    def __init__(self, element):
457        self.Id = element.get('id')
458        self.className = element.get('className')
459        self.functionId = element.get('function')
460        self.function = None
461        self.bodyStartId = element.get('bodyStart')
462        self.bodyStart = None
463        self.bodyEndId = element.get('bodyEnd')
464        self.bodyEnd = None
465        self.nestedInId = element.get('nestedIn')
466        self.nestedIn = None
467        self.type = element.get('type')
468        self.isExecutable = (self.type in ('Function', 'If', 'Else', 'For', 'While', 'Do',
469                                           'Switch', 'Try', 'Catch', 'Unconditional', 'Lambda'))
470        self.varlistId = list()
471        self.varlist = list()
472
473    def __repr__(self):
474        attrs = ["Id", "className", "functionId", "bodyStartId", "bodyEndId",
475                 "nestedInId", "nestedIn", "type", "isExecutable"]
476        return "{}({})".format(
477            "Scope",
478            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
479        )
480
481    def setId(self, IdMap):
482        self.bodyStart = IdMap[self.bodyStartId]
483        self.bodyEnd = IdMap[self.bodyEndId]
484        self.nestedIn = IdMap[self.nestedInId]
485        self.function = IdMap[self.functionId]
486        for v in self.varlistId:
487            value = IdMap.get(v)
488            if value:
489                self.varlist.append(value)
490
491
492class Function:
493    """
494    Information about a function
495    C++ class:
496    https://cppcheck.sourceforge.io/devinfo/doxyoutput/classFunction.html
497
498    Attributes
499        argument                Argument list
500        token                   Token in function implementation
501        tokenDef                Token in function definition
502        isVirtual               Is this function is virtual
503        isImplicitlyVirtual     Is this function is virtual this in the base classes
504        isInlineKeyword         Is inline keyword used
505        isStatic                Is this function static?
506    """
507
508    Id = None
509    argument = None
510    argumentId = None
511    token = None
512    tokenId = None
513    tokenDef = None
514    tokenDefId = None
515    name = None
516    type = None
517    isVirtual = None
518    isImplicitlyVirtual = None
519    isInlineKeyword = None
520    isStatic = None
521    nestedIn = None
522
523    def __init__(self, element, nestedIn):
524        self.Id = element.get('id')
525        self.tokenId = element.get('token')
526        self.tokenDefId = element.get('tokenDef')
527        self.name = element.get('name')
528        self.type = element.get('type')
529        self.isImplicitlyVirtual = element.get('isImplicitlyVirtual', 'false') == 'true'
530        self.isVirtual = element.get('isVirtual', 'false') == 'true'
531        self.isInlineKeyword = element.get('isInlineKeyword', 'false') == 'true'
532        self.isStatic = element.get('isStatic', 'false') == 'true'
533        self.nestedIn = nestedIn
534
535        self.argument = {}
536        self.argumentId = {}
537
538    def __repr__(self):
539        attrs = ["Id", "tokenId", "tokenDefId", "name", "type", "isVirtual",
540                 "isImplicitlyVirtual", "isInlineKeyword", "isStatic", "argumentId"]
541        return "{}({})".format(
542            "Function",
543            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
544        )
545
546    def setId(self, IdMap):
547        for argnr, argid in self.argumentId.items():
548            self.argument[argnr] = IdMap[argid]
549        self.token = IdMap.get(self.tokenId, None)
550        self.tokenDef = IdMap[self.tokenDefId]
551
552
553class Variable:
554    """
555    Information about a variable
556    C++ class:
557    https://cppcheck.sourceforge.io/devinfo/doxyoutput/classVariable.html
558
559    Attributes:
560        nameToken       Name token in variable declaration
561        typeStartToken  Start token of variable declaration
562        typeEndToken    End token of variable declaration
563        access          Global/Local/Namespace/Public/Protected/Public/Throw/Argument
564        scope           Variable scope
565        isArgument      Is this variable a function argument?
566        isArray         Is this variable an array?
567        isClass         Is this variable a class or struct?
568        isConst         Is this variable a const variable?
569        isGlobal        Is this variable a global variable?
570        isExtern        Is this variable an extern variable?
571        isLocal         Is this variable a local variable?
572        isPointer       Is this variable a pointer
573        isReference     Is this variable a reference
574        isStatic        Is this variable static?
575        constness       Variable constness (same encoding as ValueType::constness)
576    """
577
578    Id = None
579    nameTokenId = None
580    nameToken = None
581    typeStartTokenId = None
582    typeStartToken = None
583    typeEndTokenId = None
584    typeEndToken = None
585    access = None
586    scopeId = None
587    scope = None
588    isArgument = False
589    isArray = False
590    isClass = False
591    isConst = False
592    isExtern = False
593    isGlobal = False
594    isLocal = False
595    isPointer = False
596    isReference = False
597    isStatic = False
598    constness = 0
599
600    def __init__(self, element):
601        self.Id = element.get('id')
602        self.nameTokenId = element.get('nameToken')
603        self.nameToken = None
604        self.typeStartTokenId = element.get('typeStartToken')
605        self.typeStartToken = None
606        self.typeEndTokenId = element.get('typeEndToken')
607        self.typeEndToken = None
608        self.access = element.get('access')
609        self.scopeId = element.get('scope')
610        self.scope = None
611        self.isArgument = (self.access and self.access == 'Argument')
612        self.isArray = element.get('isArray') == 'true'
613        self.isClass = element.get('isClass') == 'true'
614        self.isConst = element.get('isConst') == 'true'
615        self.isGlobal = (self.access and self.access == 'Global')
616        self.isExtern = element.get('isExtern') == 'true'
617        self.isLocal = (self.access and self.access == 'Local')
618        self.isPointer = element.get('isPointer') == 'true'
619        self.isReference = element.get('isReference') == 'true'
620        self.isStatic = element.get('isStatic') == 'true'
621        self.constness = element.get('constness')
622        if self.constness:
623            self.constness = int(self.constness)
624
625    def __repr__(self):
626        attrs = ["Id", "nameTokenId", "typeStartTokenId", "typeEndTokenId",
627                 "access", "scopeId", "isArgument", "isArray", "isClass",
628                 "isConst", "isGlobal", "isExtern", "isLocal", "isPointer",
629                 "isReference", "isStatic", "constness", ]
630        return "{}({})".format(
631            "Variable",
632            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
633        )
634
635    def setId(self, IdMap):
636        self.nameToken = IdMap[self.nameTokenId]
637        self.typeStartToken = IdMap[self.typeStartTokenId]
638        self.typeEndToken = IdMap[self.typeEndTokenId]
639        self.scope = IdMap[self.scopeId]
640
641
642class TypedefInfo:
643    """
644    TypedefInfo class -- information about typedefs
645    """
646    name = None
647    used = None
648    file = None
649    linenr = None
650    column = None
651
652    def __init__(self, element):
653        self.name = element.get('name')
654        _load_location(self, element)
655        self.used = (element.get('used') == '1')
656
657class Value:
658    """
659    Value class
660
661    Attributes:
662        intvalue         integer value
663        tokvalue         token value
664        floatvalue       float value
665        containerSize    container size
666        condition        condition where this Value comes from
667        valueKind        'known' or 'possible'
668        inconclusive     Is value inconclusive?
669    """
670
671    intvalue = None
672    tokvalue = None
673    floatvalue = None
674    containerSize = None
675    condition = None
676    valueKind = None
677    inconclusive = False
678
679    def isKnown(self):
680        return self.valueKind and self.valueKind == 'known'
681
682    def isPossible(self):
683        return self.valueKind and self.valueKind == 'possible'
684
685    def isImpossible(self):
686        return self.valueKind and self.valueKind == 'impossible'
687
688    def __init__(self, element):
689        self.intvalue = element.get('intvalue')
690        if self.intvalue:
691            self.intvalue = int(self.intvalue)
692        self._tokvalueId = element.get('tokvalue')
693        self.floatvalue = element.get('floatvalue')
694        self.containerSize = element.get('container-size')
695        self.condition = element.get('condition-line')
696        if self.condition:
697            self.condition = int(self.condition)
698        if element.get('known'):
699            self.valueKind = 'known'
700        elif element.get('possible'):
701            self.valueKind = 'possible'
702        elif element.get('impossible'):
703            self.valueKind = 'impossible'
704        if element.get('inconclusive'):
705            self.inconclusive = True
706
707    def setId(self, IdMap):
708        self.tokvalue = IdMap.get(self._tokvalueId)
709
710    def __repr__(self):
711        attrs = ["intvalue", "tokvalue", "floatvalue", "containerSize",
712                    "condition", "valueKind", "inconclusive"]
713        return "{}({})".format(
714            "Value",
715            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
716        )
717
718
719class ValueFlow:
720    """
721    ValueFlow::Value class
722    Each possible value has a ValueFlow::Value item.
723    Each ValueFlow::Value either has a intvalue or tokvalue
724    C++ class:
725    https://cppcheck.sourceforge.io/devinfo/doxyoutput/classValueFlow_1_1Value.html
726
727    Attributes:
728        values    Possible values
729    """
730
731    Id = None
732    values = None
733
734    def __init__(self, element):
735        self.Id = element.get('id')
736        self.values = []
737
738    def __repr__(self):
739        attrs = ["Id", "values"]
740        return "{}({})".format(
741            "ValueFlow",
742            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
743        )
744
745
746class Suppression:
747    """
748    Suppression class
749    This class contains a suppression entry to suppress a warning.
750
751    Attributes
752      errorId     The id string of the error to suppress, can be a wildcard
753      fileName    The name of the file to suppress warnings for, can include wildcards
754      lineNumber  The number of the line to suppress warnings from, can be 0 to represent any line
755      symbolName  The name of the symbol to match warnings for, can include wildcards
756    """
757
758    errorId = None
759    fileName = None
760    lineNumber = None
761    symbolName = None
762
763    def __init__(self, element):
764        self.errorId = element.get('errorId')
765        self.fileName = element.get('fileName')
766        self.lineNumber = element.get('lineNumber')
767        self.symbolName = element.get('symbolName')
768
769    def __repr__(self):
770        attrs = ['errorId' , "fileName", "lineNumber", "symbolName"]
771        return "{}({})".format(
772            "Suppression",
773            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
774        )
775
776    def isMatch(self, file, line, message, errorId):
777        if ((self.fileName is None or fnmatch(file, self.fileName))
778                and (self.lineNumber is None or int(line) == int(self.lineNumber))
779                and (self.symbolName is None or fnmatch(message, '*'+self.symbolName+'*'))
780                and fnmatch(errorId, self.errorId)):
781            return True
782        return False
783
784
785class Configuration:
786    """
787    Configuration class
788    This class contains the directives, tokens, scopes, functions,
789    variables, value flows, and suppressions for one configuration.
790
791    Attributes:
792        name          Name of the configuration, "" for default
793        directives    List of Directive items
794        macro_usage   List of used macros
795        preprocessor_if_conditions  List of preprocessor if conditions that was evaluated during preprocessing
796        tokenlist     List of Token items
797        scopes        List of Scope items
798        functions     List of Function items
799        variables     List of Variable items
800        valueflow     List of ValueFlow values
801        standards     List of Standards values
802    """
803
804    name = ''
805    directives = []
806    macro_usage = []
807    preprocessor_if_conditions = []
808    tokenlist = []
809    scopes = []
810    functions = []
811    variables = []
812    typedefInfo = []
813    valueflow = []
814    standards = None
815
816    def __init__(self, name):
817        self.name = name
818        self.directives = []
819        self.macro_usage = []
820        self.preprocessor_if_conditions = []
821        self.tokenlist = []
822        self.scopes = []
823        self.functions = []
824        self.variables = []
825        self.typedefInfo = []
826        self.valueflow = []
827        self.standards = Standards()
828
829    def set_tokens_links(self):
830        """Set next/previous links between tokens."""
831        prev = None
832        for token in self.tokenlist:
833            token.previous = prev
834            if prev:
835                prev.next = token
836            prev = token
837
838    def set_id_map(self, arguments):
839        IdMap = {None: None, '0': None, '00000000': None, '0000000000000000': None, '0x0': None}
840        for token in self.tokenlist:
841            IdMap[token.Id] = token
842        for scope in self.scopes:
843            IdMap[scope.Id] = scope
844        for function in self.functions:
845            IdMap[function.Id] = function
846        for variable in self.variables:
847            IdMap[variable.Id] = variable
848        for variable in arguments:
849            IdMap[variable.Id] = variable
850        for values in self.valueflow:
851            IdMap[values.Id] = values.values
852        for token in self.tokenlist:
853            token.setId(IdMap)
854        for scope in self.scopes:
855            scope.setId(IdMap)
856        for function in self.functions:
857            function.setId(IdMap)
858        for variable in self.variables:
859            variable.setId(IdMap)
860        for variable in arguments:
861            variable.setId(IdMap)
862
863    def setIdMap(self, functions_arguments):
864        """Set relationships between objects stored in this configuration.
865        :param functions_arguments: List of Variable objects which are function arguments
866        """
867        self.set_tokens_links()
868        self.set_id_map(functions_arguments)
869
870
871class Platform:
872    """
873    Platform class
874    This class contains type sizes
875
876    Attributes:
877        name          Name of the platform
878        char_bit      CHAR_BIT value
879        short_bit     SHORT_BIT value
880        int_bit       INT_BIT value
881        long_bit      LONG_BIT value
882        long_long_bit LONG_LONG_BIT value
883        pointer_bit   POINTER_BIT value
884    """
885
886    name = ''
887    char_bit = 0
888    short_bit = 0
889    int_bit = 0
890    long_bit = 0
891    long_long_bit = 0
892    pointer_bit = 0
893
894    def __init__(self, platformnode):
895        self.name = platformnode.get('name')
896        self.char_bit = int(platformnode.get('char_bit'))
897        self.short_bit = int(platformnode.get('short_bit'))
898        self.int_bit = int(platformnode.get('int_bit'))
899        self.long_bit = int(platformnode.get('long_bit'))
900        self.long_long_bit = int(platformnode.get('long_long_bit'))
901        self.pointer_bit = int(platformnode.get('pointer_bit'))
902
903    def __repr__(self):
904        attrs = ["name", "char_bit", "short_bit", "int_bit",
905                 "long_bit", "long_long_bit", "pointer_bit"]
906        return "{}({})".format(
907            "Platform",
908            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
909        )
910
911
912class Standards:
913    """
914    Standards class
915    This class contains versions of standards that were used for the cppcheck
916
917    Attributes:
918        c            C Standard used
919        cpp          C++ Standard used
920        posix        If Posix was used
921    """
922
923    c = ""
924    cpp = ""
925    posix = False
926
927    def set_c(self, node):
928        self.c = node.get("version")
929
930    def set_cpp(self, node):
931        self.cpp = node.get("version")
932
933    def set_posix(self, node):
934        self.posix = node.get("posix") is not None
935
936    def __repr__(self):
937        attrs = ["c", "cpp", "posix"]
938        return "{}({})".format(
939            "Standards",
940            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
941        )
942
943
944class CppcheckData:
945    """
946    Class that makes cppcheck dump data available
947    Contains a list of Configuration instances
948
949    Attributes:
950        filename          Path to Cppcheck dump file
951        rawTokens         List of rawToken elements
952        suppressions      List of Suppressions
953        files             Source files for elements occurred in this configuration
954
955    To iterate through all configurations use such code:
956    @code
957    data = cppcheckdata.parsedump(...)
958    for cfg in data.configurations:
959        print('cfg: ' + cfg.name)
960    @endcode
961
962    To iterate through all tokens in each configuration use such code:
963    @code
964    data = cppcheckdata.parsedump(...)
965    for cfg in data.configurations:
966        print('cfg: ' + cfg.name)
967        code = ''
968            for token in cfg.tokenlist:
969                code = code + token.str + ' '
970        print('    ' + code)
971    @endcode
972
973    To iterate through all scopes (functions, types, etc) use such code:
974    @code
975    data = cppcheckdata.parsedump(...)
976    for cfg in data.configurations:
977        print('cfg: ' + cfg.name)
978        for scope in cfg.scopes:
979            print('    type:' + scope.type + ' name:' + scope.className)
980    @endcode
981    """
982
983    def __init__(self, filename):
984        """
985        :param filename: Path to Cppcheck dump file
986        """
987        self.filename = filename
988        self.rawTokens = []
989        self.platform = None
990        self.suppressions = []
991        self.files = []
992
993        platform_done = False
994        rawtokens_done = False
995        suppressions_done = False
996
997        # Parse general configuration options from <dumps> node
998        # We intentionally don't clean node resources here because we
999        # want to serialize in memory only small part of the XML tree.
1000        for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')):
1001            if platform_done and rawtokens_done and suppressions_done:
1002                break
1003            if node.tag == 'platform' and event == 'start':
1004                self.platform = Platform(node)
1005                platform_done = True
1006            elif node.tag == 'rawtokens' and event == 'end':
1007                for rawtokens_node in node:
1008                    if rawtokens_node.tag == 'file':
1009                        self.files.append(rawtokens_node.get('name'))
1010                    elif rawtokens_node.tag == 'tok':
1011                        tok = Token(rawtokens_node)
1012                        tok.file = self.files[int(rawtokens_node.get('fileIndex'))]
1013                        self.rawTokens.append(tok)
1014                rawtokens_done = True
1015            elif node.tag == 'suppressions' and event == 'end':
1016                for suppressions_node in node:
1017                    self.suppressions.append(Suppression(suppressions_node))
1018                suppressions_done = True
1019
1020        global current_dumpfile_suppressions
1021        current_dumpfile_suppressions = self.suppressions
1022
1023        # Set links between rawTokens.
1024        for i in range(len(self.rawTokens)-1):
1025            self.rawTokens[i+1].previous = self.rawTokens[i]
1026            self.rawTokens[i].next = self.rawTokens[i+1]
1027
1028    @property
1029    def configurations(self):
1030        """
1031        Return the list of all available Configuration objects.
1032        """
1033        return list(self.iterconfigurations())
1034
1035    def iterconfigurations(self):
1036        """
1037        Create and return iterator for the available Configuration objects.
1038        The iterator loops over all Configurations in the dump file tree, in document order.
1039        """
1040        cfg = None
1041        cfg_arguments = []  # function arguments for Configuration node initialization
1042        cfg_function = None
1043        cfg_valueflow = None
1044
1045        # Iterating <varlist> in a <scope>.
1046        iter_scope_varlist = False
1047
1048        # Iterating <typedef-info>
1049        iter_typedef_info = False
1050
1051        # Use iterable objects to traverse XML tree for dump files incrementally.
1052        # Iterative approach is required to avoid large memory consumption.
1053        # Calling .clear() is necessary to let the element be garbage collected.
1054        for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')):
1055            # Serialize new configuration node
1056            if node.tag == 'dump':
1057                if event == 'start':
1058                    cfg = Configuration(node.get('cfg'))
1059                    continue
1060                elif event == 'end':
1061                    cfg.setIdMap(cfg_arguments)
1062                    yield cfg
1063                    cfg = None
1064                    cfg_arguments = []
1065
1066            # Parse standards
1067            elif node.tag == "standards" and event == 'start':
1068                continue
1069            elif node.tag == 'c' and event == 'start':
1070                cfg.standards.set_c(node)
1071            elif node.tag == 'cpp' and event == 'start':
1072                cfg.standards.set_cpp(node)
1073            elif node.tag == 'posix' and event == 'start':
1074                cfg.standards.set_posix(node)
1075
1076            # Parse directives list
1077            elif node.tag == 'directive' and event == 'start':
1078                cfg.directives.append(Directive(node))
1079
1080            # Parse macro usage
1081            elif node.tag == 'macro' and event == 'start':
1082                cfg.macro_usage.append(MacroUsage(node))
1083
1084            # Preprocessor #if/#elif condition
1085            elif node.tag == "if-cond" and event == 'start':
1086                cfg.preprocessor_if_conditions.append(PreprocessorIfCondition(node))
1087
1088            # Parse tokens
1089            elif node.tag == 'tokenlist' and event == 'start':
1090                continue
1091            elif node.tag == 'token' and event == 'start':
1092                cfg.tokenlist.append(Token(node))
1093
1094            # Parse scopes
1095            elif node.tag == 'scopes' and event == 'start':
1096                continue
1097            elif node.tag == 'scope' and event == 'start':
1098                cfg.scopes.append(Scope(node))
1099            elif node.tag == 'varlist':
1100                if event == 'start':
1101                    iter_scope_varlist = True
1102                elif event == 'end':
1103                    iter_scope_varlist = False
1104
1105            # Parse functions
1106            elif node.tag == 'functionList' and event == 'start':
1107                continue
1108            elif node.tag == 'function':
1109                if event == 'start':
1110                    cfg_function = Function(node, cfg.scopes[-1])
1111                    continue
1112                elif event == 'end':
1113                    cfg.functions.append(cfg_function)
1114                    cfg_function = None
1115
1116            # Parse function arguments
1117            elif node.tag == 'arg' and event == 'start':
1118                arg_nr = int(node.get('nr'))
1119                arg_variable_id = node.get('variable')
1120                cfg_function.argumentId[arg_nr] = arg_variable_id
1121
1122            # Parse variables
1123            elif node.tag == 'var' and event == 'start':
1124                if iter_scope_varlist:
1125                    cfg.scopes[-1].varlistId.append(node.get('id'))
1126                else:
1127                    var = Variable(node)
1128                    if var.nameTokenId:
1129                        cfg.variables.append(var)
1130                    else:
1131                        cfg_arguments.append(var)
1132
1133            # Parse typedef info
1134            elif node.tag == 'typedef-info':
1135                iter_typedef_info = (event == 'start')
1136            elif iter_typedef_info and node.tag == 'info' and event == 'start':
1137                cfg.typedefInfo.append(TypedefInfo(node))
1138
1139            # Parse valueflows (list of values)
1140            elif node.tag == 'valueflow' and event == 'start':
1141                continue
1142            elif node.tag == 'values':
1143                if event == 'start':
1144                    cfg_valueflow = ValueFlow(node)
1145                    continue
1146                elif event == 'end':
1147                    cfg.valueflow.append(cfg_valueflow)
1148                    cfg_valueflow = None
1149
1150            # Parse values
1151            elif node.tag == 'value' and event == 'start':
1152                cfg_valueflow.values.append(Value(node))
1153
1154            # Remove links to the sibling nodes
1155            node.clear()
1156
1157    def __repr__(self):
1158        attrs = ["configurations", "platform"]
1159        return "{}({})".format(
1160            "CppcheckData",
1161            ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs))
1162        )
1163
1164
1165# Get function arguments
1166def getArgumentsRecursive(tok, arguments):
1167    if tok is None:
1168        return
1169    if tok.str == ',':
1170        getArgumentsRecursive(tok.astOperand1, arguments)
1171        getArgumentsRecursive(tok.astOperand2, arguments)
1172    else:
1173        arguments.append(tok)
1174
1175
1176def getArguments(ftok):
1177    if (not ftok.isName) or (ftok.next is None) or ftok.next.str != '(':
1178        return None
1179    args = []
1180    getArgumentsRecursive(ftok.next.astOperand2, args)
1181    return args
1182
1183
1184def parsedump(filename):
1185    """
1186    parse a cppcheck dump file
1187    """
1188    return CppcheckData(filename)
1189
1190
1191def astIsFloat(token):
1192    """
1193    Check if type of ast node is float/double
1194    """
1195
1196    if not token:
1197        return False
1198    if token.str == '.':
1199        return astIsFloat(token.astOperand2)
1200    if token.str in '+-*/%':
1201        return astIsFloat(token.astOperand1) or astIsFloat(token.astOperand2)
1202    if not token.variable:
1203        # float literal?
1204        if token.str[0].isdigit():
1205            for c in token.str:
1206                if c == 'f' or c == '.' or c == 'E':
1207                    return True
1208        return False
1209    typeToken = token.variable.typeStartToken
1210    endToken = token.variable.typeEndToken
1211    while typeToken != endToken:
1212        if typeToken.str == 'float' or typeToken.str == 'double':
1213            return True
1214        typeToken = typeToken.next
1215    if typeToken.str == 'float' or typeToken.str == 'double':
1216        return True
1217    return False
1218
1219
1220class CppCheckFormatter(argparse.HelpFormatter):
1221    """
1222    Properly formats multiline argument helps
1223    """
1224    def _split_lines(self, text, width):
1225        # this is the RawTextHelpFormatter._split_lines
1226        if text.startswith('R|'):
1227            return text[2:].splitlines()
1228        return argparse.HelpFormatter._split_lines(self, text, width)
1229
1230
1231def ArgumentParser():
1232    """
1233    Returns an argparse argument parser with an already-added
1234    argument definition for -t/--template
1235    """
1236    parser = argparse.ArgumentParser(formatter_class=CppCheckFormatter)
1237    parser.add_argument('-t', '--template', metavar='<text>',
1238                        default='{callstack}: ({severity}) {message}',
1239                        help="R|Format the error messages. E.g.\n"
1240                        "'{file}:{line},{severity},{id},{message}' or\n"
1241                        "'{file}({line}):({severity}) {message}' or\n"
1242                        "'{callstack} {message}'\n"
1243                        "Pre-defined templates: gcc, vs, edit")
1244    parser.add_argument("dumpfile", nargs='*',
1245                        help="Path of dump files from cppcheck.")
1246    parser.add_argument("--cli",
1247                        help="Addon is executed from Cppcheck",
1248                        action="store_true")
1249    parser.add_argument("--file-list", metavar='<text>',
1250                        default=None,
1251                        help="file list in a text file")
1252    parser.add_argument("-q", "--quiet",
1253                        help='do not print "Checking ..." lines',
1254                        action="store_true")
1255    return parser
1256
1257
1258def get_files(args):
1259    """Return dump_files, ctu_info_files"""
1260    all_files = args.dumpfile
1261    if args.file_list:
1262        with open(args.file_list, 'rt') as f:
1263            for line in f.readlines():
1264                all_files.append(line.rstrip())
1265    dump_files = []
1266    ctu_info_files = []
1267    for f in all_files:
1268        if f.endswith('.ctu-info'):
1269            ctu_info_files.append(f)
1270        else:
1271            dump_files.append(f)
1272    return dump_files, ctu_info_files
1273
1274
1275def simpleMatch(token, pattern):
1276    for p in pattern.split(' '):
1277        if not token or token.str != p:
1278            return False
1279        token = token.next
1280    return True
1281
1282def get_function_call_name_args(token):
1283    """Get function name and arguments for function call
1284    name, args = get_function_call_name_args(tok)
1285    """
1286    if token is None:
1287        return None, None
1288    if not token.isName or not token.scope.isExecutable:
1289        return None, None
1290    if not simpleMatch(token.next, '('):
1291        return None, None
1292    if token.function:
1293        nametok = token.function.token
1294        if nametok is None:
1295            nametok = token.function.tokenDef
1296        if token in (token.function.token, token.function.tokenDef):
1297            return None, None
1298        name = nametok.str
1299        while nametok.previous and nametok.previous.previous and nametok.previous.str == '::' and nametok.previous.previous.isName:
1300            name = nametok.previous.previous.str + '::' + name
1301            nametok = nametok.previous.previous
1302        scope = token.function.nestedIn
1303        while scope:
1304            if scope.className:
1305                name = scope.className + '::' + name
1306            scope = scope.nestedIn
1307    else:
1308        nametok = token
1309        name = nametok.str
1310        while nametok.previous and nametok.previous.previous and nametok.previous.str == '::' and nametok.previous.previous.isName:
1311            name = nametok.previous.previous.str + '::' + name
1312            nametok = nametok.previous.previous
1313    return name, getArguments(token)
1314
1315def is_suppressed(location, message, errorId):
1316    for suppression in current_dumpfile_suppressions:
1317        if suppression.isMatch(location.file, location.linenr, message, errorId):
1318            return True
1319    return False
1320
1321def reportError(location, severity, message, addon, errorId, extra=''):
1322    if '--cli' in sys.argv:
1323        msg = { 'file': location.file,
1324                'linenr': location.linenr,
1325                'column': location.column,
1326                'severity': severity,
1327                'message': message,
1328                'addon': addon,
1329                'errorId': errorId,
1330                'extra': extra}
1331        sys.stdout.write(json.dumps(msg) + '\n')
1332    else:
1333        if is_suppressed(location, message, '%s-%s' % (addon, errorId)):
1334            return
1335        loc = '[%s:%i]' % (location.file, location.linenr)
1336        if len(extra) > 0:
1337            message += ' (' + extra + ')'
1338        sys.stderr.write('%s (%s) %s [%s-%s]\n' % (loc, severity, message, addon, errorId))
1339        global EXIT_CODE
1340        EXIT_CODE = 1
1341
1342def reportSummary(dumpfile, summary_type, summary_data):
1343    # dumpfile ends with ".dump"
1344    ctu_info_file = dumpfile[:-4] + "ctu-info"
1345    with open(ctu_info_file, 'at') as f:
1346        msg = {'summary': summary_type, 'data': summary_data}
1347        f.write(json.dumps(msg) + '\n')
1348