## @mainpage cppcheckdata # # @brief This is a Python module that helps you access Cppcheck dump data.

# # License: No restrictions, use this as you need.

# import xml.etree.ElementTree as ET import argparse ## Directive class. Contains information about each preprocessor directive in the source code. # # file and linenr denote the location where the 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 # class Directive: ## The directive line, with all C or C++ comments removed str = None ## name of (possibly included) file where directive is defined file = None ## line number in (possibly included) file where directive is defined linenr = None def __init__(self, element): self.str = element.get('str') self.file = element.get('file') self.linenr = element.get('linenr') ## Token class. Contains information about each token in the source code. # # The CppcheckData.tokenlist is a list of Token items # # C++ class: http://cppcheck.net/devinfo/doxyoutput/classToken.html # # 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 # class Token: Id = None ## Token string str = None ## Next token in tokenlist. For last token, next is None. next = None ## Previous token in tokenlist. For first token, previous is None, previous = None linkId = None ## Linked token in tokenlist. Each '(', '[' and '{' are linked to the # corresponding '}', ']' and ')'. For templates, the '<' is linked to # the corresponding '>'. link = None scopeId = None ## Scope information for this token. See the Scope class. scope = None ## Is this token a symbol name isName = False ## Is this token a number, for example 123, 12.34 isNumber = False ## Is this token a int value such as 1234 isInt = False ## Is this token a int value such as 12.34 isFloat = False ## Is this token a string literal such as "hello" isString = False ## string length for string literal strlen = None ## Is this token a char literal such as 'x' isChar = False ## Is this token a operator isOp = False ## Is this token a arithmetic operator isArithmeticalOp = False ## Is this token a assignment operator isAssignmentOp = False ## Is this token a comparison operator isComparisonOp = False ## Is this token a logical operator: && || isLogicalOp = False ## varId for token, each variable has a unique non-zero id varId = None variableId = None ## Variable information for this token. See the Variable class. # # Example code: # @code # data = cppcheckdata.parsedump(...) # code = '' # for token in data.tokenlist: # code = code + token.str # if token.variable: # if token.variable.isLocal: # code = code + ':localvar' # if token.variable.isArgument: # code = code + ':arg' # code = code + ' ' # print(code) # @endcode variable = None functionId = None ## If this token points at a function call, this attribute has the Function # information. See the Function class. function = None valuesId = None ## Possible values of token # # Example code: # @code # data = cppcheckdata.parsedump(...) # code = '' # for token in data.tokenlist: # code = code + token.str # if token.values: # # print values.. # code = code + '{' # for value in token.values: # if value.intvalue: # code = code + str(value.intvalue) + ' ' # code = code + '}' # code = code + ' ' # print(code) # @endcode values = None typeScopeId = None ## type scope (token->type()->classScope) typeScope = None astParentId = None ## syntax tree parent astParent = None astOperand1Id = None ## syntax tree operand1 # # Example code: # @code # data = cppcheckdata.parsedump(...) # for token in data.tokenlist: # # # is this a addition? # if token.str == '+': # # # print LHS operand # print(token.astOperand1.str) # # @endcode astOperand1 = None astOperand2Id = None ## syntax tree operand2 # # Example code: # @code # data = cppcheckdata.parsedump(...) # for token in data.tokenlist: # # # is this a division? # if token.str == '/': # # # print RHS operand # print(token.astOperand2.str) # # @endcode astOperand2 = None ## file name file = None ## line number linenr = 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 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 == '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 self.linkId = element.get('link') self.link = None self.varId = element.get('varId') 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.file = element.get('file') self.linenr = element.get('linenr') 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 = IdMap[self.valuesId] self.typeScope = IdMap[self.typeScopeId] self.astParent = IdMap[self.astParentId] self.astOperand1 = IdMap[self.astOperand1Id] self.astOperand2 = IdMap[self.astOperand2Id] ## Get value if it exists # Returns None if it doesn't exist def getValue(self, v): if not self.values: return None for value in self.values: if value.intvalue == v: return value return None ## Scope. Information about global scope, function scopes, class scopes, inner scopes, etc. # C++ class: http://cppcheck.net/devinfo/doxyoutput/classScope.html class Scope: Id = None classStartId = None ## The { Token for this scope classStart = None classEndId = None ## The } Token for this scope classEnd = None ## Name of this scope. # For a function scope, this is the function name; # for a class scope, this is the class name. className = None ## Type of scope: Global, Function, Class, If, While type = None def __init__(self, element): self.Id = element.get('id') self.className = element.get('className') self.classStartId = element.get('classStart') self.classStart = None self.classEndId = element.get('classEnd') self.classEnd = None self.nestedInId = element.get('nestedId') self.nestedIn = None self.type = element.get('type') def setId(self, IdMap): self.classStart = IdMap[self.classStartId] self.classEnd = IdMap[self.classEndId] self.nestedIn = IdMap[self.nestedInId] ## Information about a function # C++ class: # http://cppcheck.net/devinfo/doxyoutput/classFunction.html class Function: Id = None argument = None argumentId = None tokenDef = None tokenDefId = None name = None def __init__(self, element): self.Id = element.get('id') self.tokenDefId = element.get('tokenDef') self.name = element.get('name') self.argument = {} self.argumentId = {} for arg in element: self.argumentId[arg.get('nr')] = arg.get('id') def setId(self, IdMap): for argnr, argid in self.argumentId.items(): self.argument[argnr] = IdMap[argid] self.tokenDef = IdMap[self.tokenDefId] ## Information about a variable # C++ class: # http://cppcheck.net/devinfo/doxyoutput/classVariable.html class Variable: Id = None nameTokenId = None ## name token in variable declaration nameToken = None typeStartTokenId = None ## start token of variable declaration typeStartToken = None typeEndTokenId = None ## end token of variable declaration typeEndToken = None ## Is this variable a function argument? isArgument = False ## Is this variable an array? isArray = False ## Is this variable a class or struct? isClass = False ## Is this variable a local variable? isLocal = False ## Is this variable a pointer isPointer = False ## Is this variable a reference isReference = False ## Is this variable static? isStatic = False 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.isArgument = element.get('isArgument') == 'true' self.isArray = element.get('isArray') == 'true' self.isClass = element.get('isClass') == 'true' self.isLocal = element.get('isLocal') == 'true' self.isPointer = element.get('isPointer') == 'true' self.isReference = element.get('isReference') == 'true' self.isStatic = element.get('isStatic') == 'true' def setId(self, IdMap): self.nameToken = IdMap[self.nameTokenId] self.typeStartToken = IdMap[self.typeStartTokenId] self.typeEndToken = IdMap[self.typeEndTokenId] ## ValueFlow class class ValueFlow: ## ValueFlow::Value class # Each possible value has a ValueFlow::Value item. # Each ValueFlow::Value either has a intvalue or tokvalue # C++ class: # http://cppcheck.net/devinfo/doxyoutput/classValueFlow_1_1Value.html class Value: ## integer value intvalue = None ## token value tokvalue = None ## condition where this Value comes from condition = None def __init__(self, element): self.intvalue = element.get('intvalue') if self.intvalue: self.intvalue = int(self.intvalue) self.tokvalue = element.get('tokvalue') self.condition = element.get('condition-line') if self.condition: self.condition = int(self.condition) Id = None ## Possible values values = None def __init__(self, element): self.Id = element.get('id') self.values = [] for value in element: self.values.append(ValueFlow.Value(value)) ## Configuration class # This class contains the directives, tokens, scopes, functions, # variables and value flows for one configuration. class Configuration: ## Name of the configuration, "" for default name = '' ## List of Directive items directives = [] ## List of Token items tokenlist = [] ## List of Scope items scopes = [] ## List of Function items functions = [] ## List of Variable items variables = [] ## List of ValueFlow values valueflow = [] def __init__(self, confignode): self.name = confignode.get('cfg') self.directives = [] self.tokenlist = [] self.scopes = [] self.functions = [] self.variables = [] self.valueflow = [] for element in confignode: if element.tag == 'directivelist': for directive in element: self.directives.append(Directive(directive)) if element.tag == 'tokenlist': for token in element: self.tokenlist.append(Token(token)) # set next/previous.. prev = None for token in self.tokenlist: token.previous = prev if prev: prev.next = token prev = token if element.tag == 'scopes': for scope in element: self.scopes.append(Scope(scope)) for functionList in scope: if functionList.tag == 'functionList': for function in functionList: self.functions.append(Function(function)) if element.tag == 'variables': for variable in element: self.variables.append(Variable(variable)) if element.tag == 'valueflow': for values in element: self.valueflow.append(ValueFlow(values)) IdMap = {} IdMap[None] = None IdMap['0'] = None for token in self.tokenlist: IdMap[token.Id] = token for scope in self.scopes: IdMap[scope.Id] = scope for function in self.functions: IdMap[function.Id] = function for variable in self.variables: 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 function in self.functions: function.setId(IdMap) for variable in self.variables: variable.setId(IdMap) ## Class that makes cppcheck dump data available # Contains a list of Configuration instances # # 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 # # class CppcheckData: ## List of Configurations configurations = [] def __init__(self, filename): self.configurations = [] data = ET.parse(filename) # root is 'dumps' node, each config has its own 'dump' subnode. for cfgnode in data.getroot(): self.configurations.append(Configuration(cfgnode)) ## parse a cppcheck dump file def parsedump(filename): return CppcheckData(filename) ## Check if type of ast node is float/double def astIsFloat(token): if not token: return False if token.str == '.': return astIsFloat(token.astOperand2) if '+-*/%'.find(token.str) == 0: if True == astIsFloat(token.astOperand1): return True return 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 # Create a cppcheck parser 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") return parser # Format an error message. def reportError(template, callstack=[], severity='', message='', id=''): ''' Format an error message according to the template. :param template: format string, or 'gcc', 'vs' or 'edit'. :param callstack: e.g. [['file1.cpp',10],['file2.h','20'], ... ] :param severity: e.g. 'error', 'warning' ... :param id: message ID. :param message: message text. ''' # expand predefined templates if template == 'gcc': template = '{file}:{line}: {severity}: {message}' elif template == 'vs': template = '{file}({line}): {severity}: {message}' elif template == 'edit': template = '{file} +{line}: {severity}: {message}' # compute 'callstack}, {file} and {line} replacements stack = ' -> '.join(['[' + f + ':' + str(l) + ']' for (f, l) in callstack]) file = callstack[-1][0] line = str(callstack[-1][1]) # format message return template.format(callstack=stack, file=file, line=line, severity=severity, message=message, id=id)