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