""" cppcheckdata This is a Python module that helps you access Cppcheck dump data. License: No restrictions, use this as you need. """ import argparse import json import os import sys import subprocess try: import pathlib except ImportError: message = "Failed to load pathlib. Upgrade Python to 3.x or install pathlib with 'pip install pathlib'." error_id = 'pythonError' if '--cli' in sys.argv: msg = { 'file': '', 'linenr': 0, 'column': 0, 'severity': 'error', 'message': message, 'addon': 'cppcheckdata', 'errorId': error_id, 'extra': ''} sys.stdout.write(json.dumps(msg) + '\n') else: sys.stderr.write('%s [%s]\n' % (message, error_id)) sys.exit(1) from xml.etree import ElementTree from fnmatch import fnmatch EXIT_CODE = 0 current_dumpfile_suppressions = [] def _load_location(location, element): """Load location from element/dict""" location.file = element.get('file') line = element.get('line') if line is None: line = element.get('linenr') if line is None: line = '0' location.linenr = int(line) location.column = int(element.get('column', '0')) class Location: """Utility location class""" file = None linenr = None column = None def __init__(self, element): _load_location(self, element) class Directive: """ Directive class. Contains information about each preprocessor directive in the source code. Attributes: str The directive line, with all C or C++ comments removed file Name of (possibly included) file where directive is defined linenr Line number in (possibly included) file where directive is defined To iterate through all directives use such code: @code data = cppcheckdata.parsedump(...) for cfg in data.configurations: for directive in cfg.directives: print(directive.str) @endcode """ #preprocessor.cpp/Preprocessor::dump str = None file = None linenr = None column = None def __init__(self, element): self.str = element.get('str') _load_location(self, element) def __repr__(self): attrs = ["str", "file", "linenr"] return "{}({})".format( "Directive", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class MacroUsage: """ Tracks preprocessor macro usage Attributes: name Name of the macro usefile useline usecolumn isKnownValue """ #preprocessor.cpp/Preprocessor::dump name = None # Macro name file = None linenr = None column = None usefile = None uselinenr = None usecolumn = None def __init__(self, element): self.name = element.get('name') _load_location(self, element) self.usefile = element.get('usefile') self.useline = element.get('useline') self.usecolumn = element.get('usecolumn') self.isKnownValue = element.get('is-known-value', 'false') == 'true' def __repr__(self): attrs = ["name", "file", "linenr", "column", "usefile", "useline", "usecolumn", "isKnownValue"] return "{}({})".format( "MacroUsage", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class PreprocessorIfCondition: """ Information about #if/#elif conditions Attributes: E result """ #preprocessor.cpp/Preprocessor::dump file = None linenr = None column = None E = None result = None def __init__(self, element): _load_location(self, element) self.E = element.get('E') self.result = int(element.get('result')) def __repr__(self): attrs = ["file", "linenr", "column", "E", "result"] return "{}({})".format( "PreprocessorIfCondition", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class ValueType: """ ValueType class. Contains (promoted) type information for each node in the AST. Attributes: type nonstd/pod/record/smart-pointer/container/iterator/void/bool/char/short/wchar_t/int/long/long long/unknown int/float/double/long double sign signed/unsigned bits pointer constness reference typeScopeId originalTypeName bool/const char */long/char */size_t/int/double/std::string/.. """ #symboldatabase.cpp/ValueType::dump type = None sign = None bits = 0 constness = 0 pointer = 0 typeScopeId = None typeScope = None originalTypeName = None def __init__(self, element): self.type = element.get('valueType-type') self.sign = element.get('valueType-sign') self.bits = int(element.get('valueType-bits', 0)) self.pointer = int(element.get('valueType-pointer', 0)) self.constness = int(element.get('valueType-constness', 0)) self.reference = element.get('valueType-reference') self.typeScopeId = element.get('valueType-typeScope') self.originalTypeName = element.get('valueType-originalTypeName') #valueType-containerId TODO add def __repr__(self): attrs = ["type", "sign", "bits", "typeScopeId", "originalTypeName", "constness", "pointer"] return "{}({})".format( "ValueType", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) def setId(self, IdMap): self.typeScope = IdMap[self.typeScopeId] def isIntegral(self): return self.type in {'bool', 'char', 'short', 'int', 'long', 'long long'} def isFloat(self): return self.type in {'float', 'double', 'long double'} def isEnum(self): return self.typeScope and self.typeScope.type == "Enum" class Token: """ Token class. Contains information about each token in the source code. The CppcheckData.tokenlist is a list of Token items C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classToken.html Attributes: str Token string next Next token in tokenlist. For last token, next is None. previous Previous token in tokenlist. For first token, previous is None. link Linked token in tokenlist. Each '(', '[' and '{' are linked to the corresponding '}', ']' and ')'. For templates, the '<' is linked to the corresponding '>'. scope Scope information for this token. See the Scope class. type Type information: name/op/number/string/.. isName Is this token a symbol name isUnsigned Is this token a unsigned type isSigned Is this token a signed type isNumber Is this token a number, for example 123, 12.34 isInt Is this token a int value such as 1234 isFloat Is this token a float value such as 12.34 isString Is this token a string literal such as "hello" strlen string length for string literal isChar Is this token a char literal such as 'x' isBoolean Is this token a boolean isOp Is this token a operator isArithmeticalOp Is this token a arithmetic operator isAssignmentOp Is this token a assignment operator isComparisonOp Is this token a comparison operator isLogicalOp Is this token a logical operator: && || isCast externLang isExpandedMacro Is this token a expanded macro token macroName Macro name that this token is expanded from isRemovedVoidParameter Has void parameter been removed? isSplittedVarDeclComma Is this a comma changed to semicolon in a split variable declaration ('int a,b;' => 'int a; int b;') isSplittedVarDeclEq Is this a '=' changed to semicolon in a split variable declaration ('int a=5;' => 'int a; a=5;') isImplicitInt Is this token an implicit "int"? isComplex isRestrict isAttributeExport varId varId for token, each variable has a unique non-zero id exprId exprId for token, each expression has a unique non-zero id variable Variable information for this token. See the Variable class. function If this token points at a function call, this attribute has the Function information. See the Function class. values Possible/Known values of token typeScope type scope (token->type()->classScope) astParent ast parent astOperand1 ast operand1 astOperand2 ast operand2 orriginalName orriginal name of the token valueType type information: container/.. file file name linenr line number column column To iterate through all tokens use such code: @code data = cppcheckdata.parsedump(...) for cfg in data.configurations: code = '' for token in cfg.tokenlist: code = code + token.str + ' ' print(code) @endcode """ #tokenize.cpp/Tokenizer::dump Id = None str = None next = None previous = None linkId = None link = None scopeId = None scope = None isName = False isNumber = False isInt = False isFloat = False isString = False strlen = None isChar = False isBoolean = False isOp = False isArithmeticalOp = False isAssignmentOp = False isComparisonOp = False isLogicalOp = False isCast = False isUnsigned = False isSigned = False macroName = None isExpandedMacro = False isRemovedVoidParameter = False isSplittedVarDeclComma = False isSplittedVarDeclEq = False isImplicitInt = False isComplex = False isRestrict = False isAttributeExport = False exprId = None varId = None variableId = None variable = None functionId = None function = None valuesId = None values = None impossible_values = None valueType = None typeScopeId = None typeScope = None astParentId = None astParent = None astOperand1Id = None astOperand1 = None astOperand2Id = None astOperand2 = None file = None linenr = None column = None def __init__(self, element): self.Id = element.get('id') self.str = element.get('str') self.next = None self.previous = None self.scopeId = element.get('scope') self.scope = None type = element.get('type') if type == 'name': self.isName = True if element.get('isUnsigned'): self.isUnsigned = True if element.get('isSigned'): self.isSigned = True elif type == 'number': self.isNumber = True if element.get('isInt'): self.isInt = True elif element.get('isFloat'): self.isFloat = True elif type == 'string': self.isString = True self.strlen = int(element.get('strlen')) elif type == 'char': self.isChar = True elif type == 'boolean': self.isBoolean = True elif type == 'op': self.isOp = True if element.get('isArithmeticalOp'): self.isArithmeticalOp = True elif element.get('isAssignmentOp'): self.isAssignmentOp = True elif element.get('isComparisonOp'): self.isComparisonOp = True elif element.get('isLogicalOp'): self.isLogicalOp = True if element.get('isCast'): self.isCast = True self.externLang = element.get('externLang') self.macroName = element.get('macroName') if self.macroName or element.get('isExpandedMacro'): self.isExpandedMacro = True if element.get('isRemovedVoidParameter'): self.isRemovedVoidParameter = True if element.get('isSplittedVarDeclComma'): self.isSplittedVarDeclComma = True if element.get('isSplittedVarDeclEq'): self.isSplittedVarDeclEq = True if element.get('isImplicitInt'): self.isImplicitInt = True if element.get('isComplex'): self.isComplex = True if element.get('isRestrict'): self.isRestrict = True if element.get('isAttributeExport'): self.isAttributeExport = True self.linkId = element.get('link') self.link = None if element.get('varId'): self.varId = int(element.get('varId')) if element.get('exprId'): self.exprId = int(element.get('exprId')) self.variableId = element.get('variable') self.variable = None self.functionId = element.get('function') self.function = None self.valuesId = element.get('values') self.values = None self.typeScopeId = element.get('type-scope') self.typeScope = None self.astParentId = element.get('astParent') self.astParent = None self.astOperand1Id = element.get('astOperand1') self.astOperand1 = None self.astOperand2Id = element.get('astOperand2') self.astOperand2 = None self.originalName = element.get('originalName') if element.get('valueType-type'): self.valueType = ValueType(element) else: self.valueType = None _load_location(self, element) def __repr__(self): attrs = ["Id", "str", "scopeId", "isName", "isUnsigned", "isSigned", "isNumber", "isInt", "isFloat", "isString", "strlen", "isChar", "isBoolean", "isOp", "isArithmeticalOp", "isAssignmentOp", "isComparisonOp", "isLogicalOp", "isCast", "externLang", "isExpandedMacro", "isRemovedVoidParameter", "isSplittedVarDeclComma", "isSplittedVarDeclEq", "isImplicitInt", "isComplex", "isRestrict", "isAttributeExport", "linkId", "varId", "variableId", "functionId", "valuesId", "valueType", "typeScopeId", "astParentId", "astOperand1Id", "file", "linenr", "column"] return "{}({})".format( "Token", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) def setId(self, IdMap): self.scope = IdMap[self.scopeId] self.link = IdMap[self.linkId] self.variable = IdMap[self.variableId] self.function = IdMap[self.functionId] self.values = [] self.impossible_values = [] if IdMap[self.valuesId]: for v in IdMap[self.valuesId]: if v.isImpossible(): self.impossible_values.append(v) else: self.values.append(v) v.setId(IdMap) self.typeScope = IdMap[self.typeScopeId] self.astParent = IdMap[self.astParentId] self.astOperand1 = IdMap[self.astOperand1Id] self.astOperand2 = IdMap[self.astOperand2Id] if self.valueType: self.valueType.setId(IdMap) def getValue(self, v): """ Get value if it exists Returns None if it doesn't exist """ if not self.values: return None for value in self.values: if value.intvalue == v: return value return None def getKnownIntValue(self): """ If token has a known int value then return that. Otherwise returns None """ if not self.values: return None for value in self.values: if value.valueKind == 'known': return value.intvalue return None def isUnaryOp(self, op): return self.astOperand1 and (self.astOperand2 is None) and self.str == op def isBinaryOp(self): return self.astOperand1 and self.astOperand2 def forward(self, end=None): token = self while token and token != end: yield token token = token.next def backward(self, start=None): token = self while token and token != start: yield token token = token.previous def astParents(self): token = self while token and token.astParent: token = token.astParent yield token def astTop(self): top = None for parent in self.astParents(): top = parent return top def tokAt(self, n): tl = self.forward() if n < 0: tl = self.backward() n = -n for i, t in enumerate(tl): if i == n: return t def linkAt(self, n): token = self.tokAt(n) if token: return token.link return None class Scope: """ Scope. Information about global scope, function scopes, class scopes, inner scopes, etc. C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classScope.html Attributes bodyStart The { Token for this scope bodyEnd The } Token for this scope className Name of this scope. For a function scope, this is the function name; For a class scope, this is the class name. function If this scope belongs at a function call, this attribute has the Function information. See the Function class. functions if this is a Class type, it may have functions defined nestedIn type Type of scope: Function, If/Else/For/While/Switch/Global/Enum/Struct/Namespace/Class/Constructor/Destructor isExecutable True when the type is: Function/If/Else/For/While/Do/Switch/Try/Catch/Unconditional/Lambda definedType """ #symboldatabase.cpp/SymbolDatabase::printXml Id = None bodyStartId = None bodyStart = None bodyEndId = None bodyEnd = None className = None functionId = None function = None nestedInId = None nestedIn = None nestedList = None type = None isExecutable = None varlistId = None varlist = None def __init__(self, element): self.Id = element.get('id') self.className = element.get('className') self.functionId = element.get('function') self.function = None self.functions = [] self.bodyStartId = element.get('bodyStart') self.bodyStart = None self.bodyEndId = element.get('bodyEnd') self.bodyEnd = None self.nestedInId = element.get('nestedIn') self.nestedIn = None self.nestedList = list() self.type = element.get('type') self.definedType = element.get('definedType') self.isExecutable = (self.type in ('Function', 'If', 'Else', 'For', 'While', 'Do', 'Switch', 'Try', 'Catch', 'Unconditional', 'Lambda')) self.varlistId = list() self.varlist = list() def __repr__(self): attrs = ["Id", "className", "functionId", "bodyStartId", "bodyEndId", "nestedInId", "nestedIn", "type", "definedType", "isExecutable", "functions"] return "{}({})".format( "Scope", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) def setId(self, IdMap): self.bodyStart = IdMap[self.bodyStartId] self.bodyEnd = IdMap[self.bodyEndId] self.nestedIn = IdMap[self.nestedInId] if self.nestedIn: self.nestedIn.nestedList.append(self) self.function = IdMap[self.functionId] for v in self.varlistId: value = IdMap.get(v) if value: self.varlist.append(value) class Function: """ Information about a function C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classFunction.html Attributes argument Argument list (dict of argument number and variable) token Token in function implementation tokenDef Token in function definition name type Constructor/CopyConstructor/MoveConstructor/OperatorEqual/Destructor/Function/Lambda/Unknown hasVirtualSpecifier Is this function is virtual isImplicitlyVirtual Is this function is virtual this in the base classes access Public/Protected/Private isInlineKeyword Is inline keyword used isStatic Is this function static isAttributeNoreturn overriddenFunction """ #symboldatabase.cpp/SymbolDatabase::printXml Id = None argument = None argumentId = None token = None tokenId = None tokenDef = None tokenDefId = None name = None type = None access = None isImplicitlyVirtual = None hasVirtualSpecifier = None isInlineKeyword = None isStatic = None isAttributeNoreturn = None overriddenFunction = None nestedIn = None def __init__(self, element, nestedIn): self.Id = element.get('id') self.tokenId = element.get('token') self.tokenDefId = element.get('tokenDef') self.name = element.get('name') self.type = element.get('type') self.hasVirtualSpecifier = element.get('hasVirtualSpecifier', 'false') == 'true' self.isImplicitlyVirtual = element.get('isImplicitlyVirtual', 'false') == 'true' self.access = element.get('access') self.isInlineKeyword = element.get('isInlineKeyword', 'false') == 'true' self.isStatic = element.get('isStatic', 'false') == 'true' self.isAttributeNoreturn = element.get('isAttributeNoreturn', 'false') == 'true' self.overriddenFunction = element.get('overriddenFunction', 'false') == 'true' self.nestedIn = nestedIn self.argument = {} self.argumentId = {} def __repr__(self): attrs = ["Id", "tokenId", "tokenDefId", "name", "type", "hasVirtualSpecifier", "isImplicitlyVirtual", "access", "isInlineKeyword", "isStatic", "isAttributeNoreturn", "overriddenFunction", "nestedIn", "argumentId"] return "{}({})".format( "Function", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) def setId(self, IdMap): for argnr, argid in self.argumentId.items(): self.argument[argnr] = IdMap[argid] self.token = IdMap.get(self.tokenId, None) self.tokenDef = IdMap[self.tokenDefId] #todo add class Types: #symboldatabase.cpp/SymbolDatabase::printXml class Variable: """ Information about a variable C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classVariable.html Attributes: nameToken Name token in variable declaration typeStartToken Start token of variable declaration typeEndToken End token of variable declaration access Global/Local/Namespace/Public/Protected/Public/Throw/Argument/Unknown scope Variable scope constness Variable constness (same encoding as ValueType::constness) isArgument Is this variable a function argument? isGlobal Is this variable a global variable? isLocal Is this variable a local variable? isArray Is this variable an array? isClass Is this variable a class or struct? isConst Is this variable a const variable? isExtern Is this variable an extern variable? isPointer Is this variable a pointer isReference Is this variable a reference isStatic Is this variable static? isVolatile Is this variable volatile? """ #symboldatabase.cpp/SymbolDatabase::printXml Id = None nameTokenId = None nameToken = None typeStartTokenId = None typeStartToken = None typeEndTokenId = None typeEndToken = None access = None scopeId = None scope = None isArgument = False isArray = False isClass = False isConst = False isExtern = False isGlobal = False isLocal = False isPointer = False isReference = False isStatic = False isVolatile = False constness = 0 def __init__(self, element): self.Id = element.get('id') self.nameTokenId = element.get('nameToken') self.nameToken = None self.typeStartTokenId = element.get('typeStartToken') self.typeStartToken = None self.typeEndTokenId = element.get('typeEndToken') self.typeEndToken = None self.access = element.get('access') self.isArgument = (self.access and self.access == 'Argument') self.isGlobal = (self.access and self.access == 'Global') self.isLocal = (self.access and self.access == 'Local') self.scopeId = element.get('scope') self.scope = None self.constness = int(element.get('constness',0)) self.isArray = element.get('isArray') == 'true' self.isClass = element.get('isClass') == 'true' self.isConst = element.get('isConst') == 'true' self.isExtern = element.get('isExtern') == 'true' self.isPointer = element.get('isPointer') == 'true' self.isReference = element.get('isReference') == 'true' self.isStatic = element.get('isStatic') == 'true' self.isVolatile = element.get('isVolatile') == 'true' def __repr__(self): attrs = ["Id", "nameTokenId", "typeStartTokenId", "typeEndTokenId", "access", "scopeId", "isArgument", "isArray", "isClass", "isConst", "isGlobal", "isExtern", "isLocal", "isPointer", "isReference", "isStatic", "isVolatile", "constness"] return "{}({})".format( "Variable", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) def setId(self, IdMap): self.nameToken = IdMap[self.nameTokenId] self.typeStartToken = IdMap[self.typeStartTokenId] self.typeEndToken = IdMap[self.typeEndTokenId] self.scope = IdMap[self.scopeId] class Container: """ Container class -- information about containers Attributes: array-like-index-op true/false stdStringLike true/false """ #tokenizer.cpp/tokenizer::dump Id = None def __init__(self, element): self.Id = element.get('id') self.arrayLikeIndexOp = element.get('array-like-index-op') == 'true' self.stdStringLike = element.get('std-string-like') == 'true' class TypedefInfo: """ TypedefInfo class -- information about typedefs Attributes: name name of the typedef used 0/1 """ #tokenizer.cpp/tokenizer::dump name = None used = None file = None linenr = None column = None def __init__(self, element): self.name = element.get('name') _load_location(self, element) self.used = (element.get('used') == '1') class Value: """ Value class Attributes: intvalue integer value tokvalue token value floatvalue float value movedvalue uninit containerSize container size bufferSize buffer size lifetimeScope Local/Argument/SubFunction/ThisPointer/ThisValue lifetimeKind Object/SubObject/Lambda/Iterator/Address symbolicDelta condition condition where this Value comes from bound Upper/Lower/Point valueKind known/possible/impossible/inconclusive path 0/1/2/3/.. """ #token.cpp/token::printValueFlow intvalue = None tokvalue = None floatvalue = None containerSize = None condition = None valueKind = None def isKnown(self): return self.valueKind and self.valueKind == 'known' def isPossible(self): return self.valueKind and self.valueKind == 'possible' def isImpossible(self): return self.valueKind and self.valueKind == 'impossible' def isInconclusive(self): return self.valueKind and self.valueKind == 'inconclusive' def __init__(self, element): self.intvalue = element.get('intvalue') if self.intvalue: self.intvalue = int(self.intvalue) self._tokvalueId = element.get('tokvalue') self.floatvalue = element.get('floatvalue') self.movedvalue = element.get('movedvalue') self.uninit = element.get('uninit') self.bufferSize = element.get('buffer-size') self.containerSize = element.get('container-size') self.iteratorStart = element.get('iterator-start') self.iteratorEnd = element.get('iterator-end') self._lifetimeId = element.get('lifetime') self.lifetimeScope = element.get('lifetime-scope') self.lifetimeKind = element.get('lifetime-kind') self._symbolicId = element.get('symbolic') self.symbolicDelta = element.get('symbolic-delta') self.bound = element.get('bound') self.condition = element.get('condition-line') if self.condition: self.condition = int(self.condition) if element.get('known'): self.valueKind = 'known' elif element.get('possible'): self.valueKind = 'possible' elif element.get('impossible'): self.valueKind = 'impossible' elif element.get('inconclusive'): self.valueKind = 'inconclusive' self.path = element.get('path') def setId(self, IdMap): self.tokvalue = IdMap.get(self._tokvalueId) self.lifetime = IdMap.get(self._lifetimeId) self.symbolic = IdMap.get(self._symbolicId) def __repr__(self): attrs = ["intvalue", "tokvalue", "floatvalue", "movedvalue", "uninit", "bufferSize", "containerSize", "condition", "valueKind"] return "{}({})".format( "Value", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class ValueFlow: """ ValueFlow::Value class Each possible value has a ValueFlow::Value item. Each ValueFlow::Value either has a intvalue or tokvalue C++ class: https://cppcheck.sourceforge.io/devinfo/doxyoutput/classValueFlow_1_1Value.html Attributes: values Possible values """ Id = None values = None def __init__(self, element): self.Id = element.get('id') self.values = [] def __repr__(self): attrs = ["Id", "values"] return "{}({})".format( "ValueFlow", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class Suppression: """ Suppression class This class contains a suppression entry to suppress a warning. Attributes errorId The id string of the error to suppress, can be a wildcard fileName The name of the file to suppress warnings for, can include wildcards lineNumber The number of the line to suppress warnings from, can be 0 to represent any line symbolName The name of the symbol to match warnings for, can include wildcards """ errorId = None fileName = None lineNumber = None symbolName = None def __init__(self, element): self.errorId = element.get('errorId') self.fileName = element.get('fileName') self.lineNumber = element.get('lineNumber') self.symbolName = element.get('symbolName') def __repr__(self): attrs = ['errorId' , "fileName", "lineNumber", "symbolName"] return "{}({})".format( "Suppression", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) def isMatch(self, file, line, message, errorId): if ((self.fileName is None or fnmatch(file, self.fileName)) and (self.lineNumber is None or int(line) == int(self.lineNumber)) and (self.symbolName is None or fnmatch(message, '*'+self.symbolName+'*')) and fnmatch(errorId, self.errorId)): return True return False class Configuration: """ Configuration class This class contains the directives, tokens, scopes, functions, variables, value flows, and suppressions for one configuration. Attributes: name Name of the configuration, "" for default directives List of Directive items macro_usage List of used macros preprocessor_if_conditions List of preprocessor if conditions that was evaluated during preprocessing tokenlist List of Token items scopes List of Scope items containers List of Container items functions List of Function items variables List of Variable items valueflow List of ValueFlow values standards List of Standards values """ name = '' directives = [] macro_usage = [] preprocessor_if_conditions = [] tokenlist = [] scopes = [] containers = [] functions = [] variables = [] typedefInfo = [] valueflow = [] standards = None clang_warnings = [] def __init__(self, name): self.name = name self.directives = [] self.macro_usage = [] self.preprocessor_if_conditions = [] self.tokenlist = [] self.scopes = [] self.containers = [] self.functions = [] self.variables = [] self.typedefInfo = [] self.valueflow = [] self.standards = Standards() self.clang_warnings = [] def set_tokens_links(self): """Set next/previous links between tokens.""" prev = None for token in self.tokenlist: token.previous = prev if prev: prev.next = token prev = token def set_id_map(self, arguments): IdMap = {None: None, '0': None, '00000000': None, '0000000000000000': None, '0x0': None} for token in self.tokenlist: IdMap[token.Id] = token for scope in self.scopes: IdMap[scope.Id] = scope for container in self.containers: IdMap[container.Id] = container for function in self.functions: IdMap[function.Id] = function for variable in self.variables: IdMap[variable.Id] = variable for variable in arguments: IdMap[variable.Id] = variable for values in self.valueflow: IdMap[values.Id] = values.values for token in self.tokenlist: token.setId(IdMap) for scope in self.scopes: scope.setId(IdMap) #for container in self.containers: # container.setId(IdMap) for function in self.functions: function.setId(IdMap) for variable in self.variables: variable.setId(IdMap) for variable in arguments: variable.setId(IdMap) def setIdMap(self, functions_arguments): """Set relationships between objects stored in this configuration. :param functions_arguments: List of Variable objects which are function arguments """ self.set_tokens_links() self.set_id_map(functions_arguments) class Platform: """ Platform class This class contains type sizes Attributes: name Name of the platform: unspecified/native/win32A/win32W/win64/unix32/unix64/platformFile char_bit CHAR_BIT value short_bit SHORT_BIT value int_bit INT_BIT value long_bit LONG_BIT value long_long_bit LONG_LONG_BIT value pointer_bit POINTER_BIT value """ name = '' char_bit = 0 short_bit = 0 int_bit = 0 long_bit = 0 long_long_bit = 0 pointer_bit = 0 def __init__(self, platformnode): self.name = platformnode.get('name') self.char_bit = int(platformnode.get('char_bit')) self.short_bit = int(platformnode.get('short_bit')) self.int_bit = int(platformnode.get('int_bit')) self.long_bit = int(platformnode.get('long_bit')) self.long_long_bit = int(platformnode.get('long_long_bit')) self.pointer_bit = int(platformnode.get('pointer_bit')) def __repr__(self): attrs = ["name", "char_bit", "short_bit", "int_bit", "long_bit", "long_long_bit", "pointer_bit"] return "{}({})".format( "Platform", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class Standards: """ Standards class This class contains versions of standards that were used for the cppcheck Attributes: c C Standard used cpp C++ Standard used posix If Posix was used """ c = "" cpp = "" posix = False def set_c(self, node): self.c = node.get("version") def set_cpp(self, node): self.cpp = node.get("version") def set_posix(self, node): self.posix = node.get("posix") is not None def __repr__(self): attrs = ["c", "cpp", "posix"] return "{}({})".format( "Standards", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) class CppcheckData: """ Class that makes cppcheck dump data available Contains a list of Configuration instances Attributes: filename Path to Cppcheck dump file rawTokens List of rawToken elements suppressions List of Suppressions files Source files for elements occurred in this configuration To iterate through all configurations use such code: @code data = cppcheckdata.parsedump(...) for cfg in data.configurations: print('cfg: ' + cfg.name) @endcode To iterate through all tokens in each configuration use such code: @code data = cppcheckdata.parsedump(...) for cfg in data.configurations: print('cfg: ' + cfg.name) code = '' for token in cfg.tokenlist: code = code + token.str + ' ' print(' ' + code) @endcode To iterate through all scopes (functions, types, etc) use such code: @code data = cppcheckdata.parsedump(...) for cfg in data.configurations: print('cfg: ' + cfg.name) for scope in cfg.scopes: print(' type:' + scope.type + ' name:' + scope.className) @endcode """ def __init__(self, filename): """ :param filename: Path to Cppcheck dump file """ self.filename = filename self.rawTokens = [] self.platform = None self.suppressions = [] self.files = [] # source files for elements occurred in this configuration platform_done = False rawtokens_done = False suppressions_done = False # Parse general configuration options from node # We intentionally don't clean node resources here because we # want to serialize in memory only small part of the XML tree. for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')): if platform_done and rawtokens_done and suppressions_done: break if node.tag == 'platform' and event == 'start': self.platform = Platform(node) platform_done = True elif node.tag == 'rawtokens' and event == 'end': for rawtokens_node in node: if rawtokens_node.tag == 'file': self.files.append(rawtokens_node.get('name')) elif rawtokens_node.tag == 'tok': tok = Token(rawtokens_node) tok.file = self.files[int(rawtokens_node.get('fileIndex'))] self.rawTokens.append(tok) rawtokens_done = True elif node.tag == 'suppressions' and event == 'end': for suppressions_node in node: self.suppressions.append(Suppression(suppressions_node)) suppressions_done = True global current_dumpfile_suppressions current_dumpfile_suppressions = self.suppressions # Set links between rawTokens. for i in range(len(self.rawTokens)-1): self.rawTokens[i+1].previous = self.rawTokens[i] self.rawTokens[i].next = self.rawTokens[i+1] @property def configurations(self): """ Return the list of all available Configuration objects. """ return list(self.iterconfigurations()) def iterconfigurations(self): """ Create and return iterator for the available Configuration objects. The iterator loops over all Configurations in the dump file tree, in document order. """ cfg = None cfg_arguments = [] # function arguments for Configuration node initialization cfg_function = None cfg_valueflow = None # Iterating in a . iter_scope_varlist = False # Iterating iter_typedef_info = False # Use iterable objects to traverse XML tree for dump files incrementally. # Iterative approach is required to avoid large memory consumption. # Calling .clear() is necessary to let the element be garbage collected. for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')): # Serialize new configuration node if node.tag == 'dump': if event == 'start': cfg = Configuration(node.get('cfg')) continue elif event == 'end': cfg.setIdMap(cfg_arguments) yield cfg cfg = None cfg_arguments = [] elif node.tag == 'clang-warning' and event == 'start': cfg.clang_warnings.append({'file': node.get('file'), 'line': int(node.get('line')), 'column': int(node.get('column')), 'message': node.get('message')}) # Parse standards elif node.tag == "standards" and event == 'start': continue elif node.tag == 'c' and event == 'start': cfg.standards.set_c(node) elif node.tag == 'cpp' and event == 'start': cfg.standards.set_cpp(node) elif node.tag == 'posix' and event == 'start': cfg.standards.set_posix(node) # Parse directives list elif node.tag == 'directive' and event == 'start': cfg.directives.append(Directive(node)) # Parse macro usage elif node.tag == 'macro' and event == 'start': cfg.macro_usage.append(MacroUsage(node)) # Preprocessor #if/#elif condition elif node.tag == "if-cond" and event == 'start': cfg.preprocessor_if_conditions.append(PreprocessorIfCondition(node)) # Parse tokens elif node.tag == 'tokenlist' and event == 'start': continue elif node.tag == 'token' and event == 'start': cfg.tokenlist.append(Token(node)) # Parse scopes elif node.tag == 'scopes' and event == 'start': continue elif node.tag == 'scope' and event == 'start': cfg.scopes.append(Scope(node)) elif node.tag == 'varlist': if event == 'start': iter_scope_varlist = True elif event == 'end': iter_scope_varlist = False # Parse functions elif node.tag == 'functionList' and event == 'start': continue elif node.tag == 'function': if event == 'start': cfg_function = Function(node, cfg.scopes[-1]) continue elif event == 'end': cfg.functions.append(cfg_function) cfg_function = None # Parse function arguments elif node.tag == 'arg' and event == 'start': arg_nr = int(node.get('nr')) arg_variable_id = node.get('variable') cfg_function.argumentId[arg_nr] = arg_variable_id # Parse variables elif node.tag == 'var' and event == 'start': if iter_scope_varlist: cfg.scopes[-1].varlistId.append(node.get('id')) else: var = Variable(node) if var.nameTokenId: cfg.variables.append(var) else: cfg_arguments.append(var) # Parse containers elif node.tag == 'containers' and event == 'start': continue elif node.tag == 'container' and event == 'start': cfg.containers.append(Container(node)) # Parse typedef info elif node.tag == 'typedef-info': iter_typedef_info = (event == 'start') elif iter_typedef_info and node.tag == 'info' and event == 'start': cfg.typedefInfo.append(TypedefInfo(node)) # Parse template-token #elif node.tag == 'TokenAndName' and event == 'start': #todo add processing of containers # cfg.containers.append(Container(node)) # Parse valueflows (list of values) elif node.tag == 'valueflow' and event == 'start': continue elif node.tag == 'values': if event == 'start': cfg_valueflow = ValueFlow(node) continue elif event == 'end': cfg.valueflow.append(cfg_valueflow) cfg_valueflow = None # Parse values elif node.tag == 'value' and event == 'start': cfg_valueflow.values.append(Value(node)) # Remove links to the sibling nodes node.clear() def __repr__(self): attrs = ["configurations", "platform"] return "{}({})".format( "CppcheckData", ", ".join(("{}={}".format(a, repr(getattr(self, a))) for a in attrs)) ) # Get function arguments def getArgumentsRecursive(tok, arguments): if tok is None: return if tok.str == ',': getArgumentsRecursive(tok.astOperand1, arguments) getArgumentsRecursive(tok.astOperand2, arguments) else: arguments.append(tok) def getArguments(ftok): if (not ftok.isName) or (ftok.next is None) or ftok.next.str != '(': return None args = [] getArgumentsRecursive(ftok.next.astOperand2, args) return args def parsedump(filename): """ parse a cppcheck dump file """ return CppcheckData(filename) def astIsFloat(token): """ Check if type of ast node is float/double """ if not token: return False if token.str == '.': return astIsFloat(token.astOperand2) if token.str in '+-*/%': return astIsFloat(token.astOperand1) or astIsFloat(token.astOperand2) if not token.variable: # float literal? if token.str[0].isdigit(): for c in token.str: if c == 'f' or c == '.' or c == 'E': return True return False typeToken = token.variable.typeStartToken endToken = token.variable.typeEndToken while typeToken != endToken: if typeToken.str == 'float' or typeToken.str == 'double': return True typeToken = typeToken.next if typeToken.str == 'float' or typeToken.str == 'double': return True return False class CppCheckFormatter(argparse.HelpFormatter): """ Properly formats multiline argument helps """ def _split_lines(self, text, width): # this is the RawTextHelpFormatter._split_lines if text.startswith('R|'): return text[2:].splitlines() return argparse.HelpFormatter._split_lines(self, text, width) def ArgumentParser(): """ Returns an argparse argument parser with an already-added argument definition for -t/--template """ parser = argparse.ArgumentParser(formatter_class=CppCheckFormatter) parser.add_argument('-t', '--template', metavar='', default='{callstack}: ({severity}) {message}', help="R|Format the error messages. E.g.\n" "'{file}:{line},{severity},{id},{message}' or\n" "'{file}({line}):({severity}) {message}' or\n" "'{callstack} {message}'\n" "Pre-defined templates: gcc, vs, edit") parser.add_argument("dumpfile", nargs='*', help="Path of dump files from cppcheck.") parser.add_argument("--cli", help="Addon is executed from Cppcheck", action="store_true") parser.add_argument("--file-list", metavar='', default=None, help="file list in a text file") parser.add_argument("-q", "--quiet", help='do not print "Checking ..." lines', action="store_true") return parser def get_files(args): """Return dump_files, ctu_info_files""" all_files = args.dumpfile if args.file_list: with open(args.file_list, 'rt') as f: for line in f.readlines(): all_files.append(line.rstrip()) dump_files = [] ctu_info_files = [] for f in all_files: if f.endswith('.ctu-info'): ctu_info_files.append(f) else: dump_files.append(f) return dump_files, ctu_info_files def simpleMatch(token, pattern): for p in pattern.split(' '): if not token or token.str != p: return False token = token.next return True patterns = { '%any%': lambda tok: tok, '%assign%': lambda tok: tok if tok.isAssignmentOp else None, '%comp%': lambda tok: tok if tok.isComparisonOp else None, '%name%': lambda tok: tok if tok.isName else None, '%op%': lambda tok: tok if tok.isOp else None, '%or%': lambda tok: tok if tok.str == '|' else None, '%oror%': lambda tok: tok if tok.str == '||' else None, '%var%': lambda tok: tok if tok.variable else None, '(*)': lambda tok: tok.link if tok.str == '(' else None, '[*]': lambda tok: tok.link if tok.str == '[' else None, '{*}': lambda tok: tok.link if tok.str == '{' else None, '<*>': lambda tok: tok.link if tok.str == '<' and tok.link else None, } def match_atom(token, p): if not token: return None if not p: return None if token.str == p: return token if p in ['!', '|', '||', '%', '!=', '*']: return None if p in patterns: return patterns[p](token) if '|' in p: for x in p.split('|'): t = match_atom(token, x) if t: return t elif p.startswith('!!'): t = match_atom(token, p[2:]) if not t: return token elif p.startswith('**'): a = p[2:] t = token while t: if match_atom(t, a): return t if t.link and t.str in ['(', '[', '<', '{']: t = t.link t = t.next return None class MatchResult: def __init__(self, matches, bindings=None, keys=None): self.__dict__.update(bindings or {}) self._matches = matches self._keys = keys or [] def __bool__(self): return self._matches def __nonzero__(self): return self._matches def __getattr__(self, k): if k in self._keys: return None else: raise AttributeError def bind_split(s): if '@' in s: p = s.partition('@') return (p[0], p[2]) return (s, None) def match(token, pattern): if not pattern: return MatchResult(False) end = None bindings = {} words = [bind_split(word) for word in pattern.split()] for p, b in words: t = match_atom(token, p) if b: bindings[b] = token if not t: return MatchResult(False, keys=[xx for pp, xx in words]+['end']) end = t token = t.next bindings['end'] = end return MatchResult(True, bindings=bindings) def get_function_call_name_args(token): """Get function name and arguments for function call name, args = get_function_call_name_args(tok) """ if token is None: return None, None if not token.isName or not token.scope.isExecutable: return None, None if not simpleMatch(token.next, '('): return None, None if token.function: nametok = token.function.token if nametok is None: nametok = token.function.tokenDef if token in (token.function.token, token.function.tokenDef): return None, None name = nametok.str while nametok.previous and nametok.previous.previous and nametok.previous.str == '::' and nametok.previous.previous.isName: name = nametok.previous.previous.str + '::' + name nametok = nametok.previous.previous scope = token.function.nestedIn while scope: if scope.className: name = scope.className + '::' + name scope = scope.nestedIn else: nametok = token name = nametok.str while nametok.previous and nametok.previous.previous and nametok.previous.str == '::' and nametok.previous.previous.isName: name = nametok.previous.previous.str + '::' + name nametok = nametok.previous.previous return name, getArguments(token) def is_suppressed(location, message, errorId): for suppression in current_dumpfile_suppressions: if suppression.isMatch(location.file, location.linenr, message, errorId): return True return False def reportError(location, severity, message, addon, errorId, extra=''): if '--cli' in sys.argv: msg = { 'file': location.file, 'linenr': location.linenr, 'column': location.column, 'severity': severity, 'message': message, 'addon': addon, 'errorId': errorId, 'extra': extra} sys.stdout.write(json.dumps(msg) + '\n') else: if is_suppressed(location, message, '%s-%s' % (addon, errorId)): return loc = '[%s:%i]' % (location.file, location.linenr) if len(extra) > 0: message += ' (' + extra + ')' sys.stderr.write('%s (%s) %s [%s-%s]\n' % (loc, severity, message, addon, errorId)) global EXIT_CODE EXIT_CODE = 1 def reportSummary(dumpfile, summary_type, summary_data): # dumpfile ends with ".dump" ctu_info_file = dumpfile[:-4] + "ctu-info" with open(ctu_info_file, 'at') as f: msg = {'summary': summary_type, 'data': summary_data} f.write(json.dumps(msg) + '\n') def get_path_premium_addon(): p = pathlib.Path(sys.argv[0]).parent.parent for ext in ('.exe', ''): p1 = os.path.join(p, 'premiumaddon' + ext) p2 = os.path.join(p, 'cppcheck' + ext) if os.path.isfile(p1) and os.path.isfile(p2): return p1 return None def cmd_output(cmd): with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: comm = p.communicate() out = comm[0] if p.returncode == 1 and len(comm[1]) > 2: out = comm[1] return out.decode(encoding='utf-8', errors='ignore')