diff --git a/addons/cert.py b/addons/cert.py index fb223f435..3e9ca3549 100755 --- a/addons/cert.py +++ b/addons/cert.py @@ -13,6 +13,7 @@ import argparse import cppcheckdata import sys import re +import subprocess VERIFY = ('-verify' in sys.argv) VERIFY_EXPECTED = [] @@ -195,7 +196,9 @@ def int31(data, platform): if to_value_type is None or not from_values: continue bits = None - if to_value_type.type == 'char': + if token.valueType.pointer > 0: + bits = platform.pointer_bit + elif to_value_type.type == 'char': bits = platform.char_bit elif to_value_type.type == 'short': bits = platform.short_bit @@ -399,6 +402,8 @@ if __name__ == '__main__': parser = get_args_parser() args = parser.parse_args() + path_premium_addon = cppcheckdata.get_path_premium_addon() + if args.verify: VERIFY = True @@ -411,6 +416,14 @@ if __name__ == '__main__': if not args.quiet: print('Checking %s...' % dumpfile) + if path_premium_addon: + premium_command = [path_premium_addon, '--cert', dumpfile] + if args.cli: + premium_command.append('--cli') + for line in subprocess.check_output(premium_command).decode('ascii').split('\n'): + if line.find('cert-') > 0: + print(line.strip()) + data = cppcheckdata.CppcheckData(dumpfile) if VERIFY: diff --git a/addons/cppcheckdata.py b/addons/cppcheckdata.py index 157e0d85e..08ba73d94 100755 --- a/addons/cppcheckdata.py +++ b/addons/cppcheckdata.py @@ -8,6 +8,8 @@ License: No restrictions, use this as you need. import argparse import json +import os +import pathlib import sys from xml.etree import ElementTree @@ -1364,3 +1366,14 @@ def reportSummary(dumpfile, summary_type, summary_data): 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 diff --git a/addons/misra.py b/addons/misra.py index f2bfbe458..89d4351ec 100755 --- a/addons/misra.py +++ b/addons/misra.py @@ -25,6 +25,7 @@ import argparse import codecs import string import copy +import subprocess try: from itertools import izip as zip @@ -422,6 +423,28 @@ def get_type_conversion_to_from(token): return None + +def is_composite_expr(expr, composite_operator=False): + """MISRA C 2012, section 8.10.3""" + if expr is None: + return False + + if not composite_operator: + if (expr.str in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~')): + return is_composite_expr(expr.astOperand1,True) or is_composite_expr(expr.astOperand2, True) + if expr.str == '?' and simpleMatch(expr.astOperand2, ':'): + colon = expr.astOperand2 + return is_composite_expr(colon.astOperand1,True) or is_composite_expr(colon.astOperand2, True) + return False + + # non constant expression? + if expr.isNumber: + return False + if expr.astOperand1 or expr.astOperand2: + return is_composite_expr(expr.astOperand1,True) or is_composite_expr(expr.astOperand2, True) + return True + + def getEssentialTypeCategory(expr): if not expr: return None @@ -1009,16 +1032,14 @@ def isNoReturnScope(tok): # Return the token which the value is assigned to -def getAssignedVariableToken(valueToken): - if not valueToken: +def getAssignedVariableToken(vartok): + if not vartok: return None - if not valueToken.astParent: - return None - operator = valueToken.astParent - if operator.isAssignmentOp: - return operator.astOperand1 - if operator.isArithmeticalOp: - return getAssignedVariableToken(operator) + parent = vartok.astParent + while parent and parent.isArithmeticalOp: + parent = parent.astParent + if parent and parent.isAssignmentOp: + return parent.astOperand1 return None # If the value is used as a return value, return the function definition @@ -1297,6 +1318,8 @@ class MisraChecker: self._ctu_summary_identifiers = False self._ctu_summary_usage = False + self.path_premium_addon = None + def __repr__(self): attrs = ["settings", "verify_expected", "verify_actual", "violations", "ruleTexts", "suppressedRules", "filePrefix", @@ -1372,7 +1395,9 @@ class MisraChecker: local_identifiers.append(identifier(var.nameToken)) elif var.isStatic: names.append(var.nameToken.str) - internal_identifiers.append(identifier(var.nameToken)) + i = identifier(var.nameToken) + i['inlinefunc'] = False + internal_identifiers.append(i) else: names.append(var.nameToken.str) i = identifier(var.nameToken) @@ -1383,9 +1408,14 @@ class MisraChecker: if func.tokenDef is None: continue if func.isStatic: - internal_identifiers.append(identifier(func.tokenDef)) - else: i = identifier(func.tokenDef) + i['inlinefunc'] = func.isInlineKeyword + internal_identifiers.append(i) + else: + if func.token is None: + i = identifier(func.tokenDef) + else: + i = identifier(func.token) i['decl'] = func.token is None external_identifiers.append(i) @@ -1404,12 +1434,12 @@ class MisraChecker: continue if token.function and token.scope.isExecutable: if (not token.function.isStatic) and (token.str not in names): - names.append(token.str) + names.append({'name': token.str, 'file': token.file}) elif token.variable: if token == token.variable.nameToken: continue if token.variable.access == 'Global' and (not token.variable.isStatic) and (token.str not in names): - names.append(token.str) + names.append({'name': token.str, 'file': token.file}) if len(names) > 0: cppcheckdata.reportSummary(dumpfile, 'MisraUsage', names) @@ -1759,41 +1789,9 @@ class MisraChecker: self.reportError(tok, 7, 1) def misra_7_2(self, data): - # Large constant numbers that are assigned to a variable should have an - # u/U suffix if the variable type is unsigned. - def reportErrorIfMissingSuffix(variable, value): - if 'U' in value.str.upper(): - return - if value and value.isNumber: - if variable and variable.valueType and variable.valueType.sign == 'unsigned': - if variable.valueType.type in ['char', 'short', 'int', 'long', 'long long']: - limit = 1 << (bitsOfEssentialType(variable.valueType.type) -1) - v = value.getKnownIntValue() - if v is not None and v >= limit: - self.reportError(value, 7, 2) - for token in data.tokenlist: - # Check normal variable assignment - if token.valueType and token.isNumber: - variable = getAssignedVariableToken(token) - reportErrorIfMissingSuffix(variable, token) - - # Check use as function parameter - if isFunctionCall(token) and token.astOperand1 and token.astOperand1.function: - functionDeclaration = token.astOperand1.function - - if functionDeclaration.tokenDef: - if functionDeclaration.tokenDef is token.astOperand1: - # Token is not a function call, but it is the definition of the function - continue - - parametersUsed = getArguments(token) - for i in range(len(parametersUsed)): - usedParameter = parametersUsed[i] - if usedParameter.isNumber: - parameterDefinition = functionDeclaration.argument.get(i+1) - if parameterDefinition and parameterDefinition.nameToken: - reportErrorIfMissingSuffix(parameterDefinition.nameToken, usedParameter) + if token.isInt and ('U' not in token.str.upper()) and token.valueType and token.valueType.sign == 'unsigned': + self.reportError(token, 7, 2) def misra_7_3(self, rawTokens): compiled = re.compile(r'^[0-9.]+[Uu]*l+[Uu]*$') @@ -2277,8 +2275,7 @@ class MisraChecker: for token in data.tokenlist: if token.str != '=' or not token.astOperand1 or not token.astOperand2: continue - if (token.astOperand2.str not in ('+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~') and - not isCast(token.astOperand2)): + if not is_composite_expr(token.astOperand2): continue vt1 = token.astOperand1.valueType vt2 = token.astOperand2.valueType @@ -4058,7 +4055,10 @@ class MisraChecker: misra_severity = self.ruleTexts[ruleNum].misra_severity cppcheck_severity = self.ruleTexts[ruleNum].cppcheck_severity elif len(self.ruleTexts) == 0: - errmsg = 'misra violation (use --rule-texts= to get proper output)' + if self.path_premium_addon: + errmsg = subprocess.check_output([self.path_premium_addon, '--get-rule-text=' + errorId]).strip().decode('ascii') + else: + errmsg = 'misra violation (use --rule-texts= to get proper output)' else: errmsg = 'misra violation %s with no text in the supplied rule-texts-file' % (ruleNum) @@ -4392,6 +4392,10 @@ class MisraChecker: self.executeCheck(2209, self.misra_22_9, cfg) self.executeCheck(2210, self.misra_22_10, cfg) + # Premium MISRA checking, deep analysis + if cfgNumber == 0 and self.path_premium_addon: + subprocess.call([self.path_premium_addon, '--misra', dumpfile]) + def analyse_ctu_info(self, ctu_info_files): all_typedef_info = [] all_tagname_info = [] @@ -4400,7 +4404,7 @@ class MisraChecker: all_external_identifiers_def = {} all_internal_identifiers = {} all_local_identifiers = {} - all_usage_count = {} + all_usage_files = {} from cppcheckdata import Location @@ -4478,8 +4482,9 @@ class MisraChecker: if summary_type == 'MisraInternalIdentifiers': for s in summary_data: if s['name'] in all_internal_identifiers: - self.reportError(Location(s), 5, 9) - self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) + if not s['inlinefunc'] or s['file'] != all_internal_identifiers[s['name']]['file']: + self.reportError(Location(s), 5, 9) + self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) all_internal_identifiers[s['name']] = s if summary_type == 'MisraLocalIdentifiers': @@ -4488,10 +4493,10 @@ class MisraChecker: if summary_type == 'MisraUsage': for s in summary_data: - if s in all_usage_count: - all_usage_count[s] += 1 + if s['name'] in all_usage_files: + all_usage_files[s['name']].append(s['file']) else: - all_usage_count[s] = 1 + all_usage_files[s['name']] = [s['file']] for ti in all_typedef_info: if not ti['used']: @@ -4518,9 +4523,12 @@ class MisraChecker: self.reportError(Location(local_identifier), 5, 8) self.reportError(Location(external_identifier), 5, 8) - for name, count in all_usage_count.items(): + for name, files in all_usage_files.items(): #print('%s:%i' % (name, count)) - if count != 1: + count = len(files) + if count != 1 or name not in all_external_identifiers_def: + continue + if files[0] != Location(all_external_identifiers_def[name]).file: continue if name in all_external_identifiers: self.reportError(Location(all_external_identifiers[name]), 8, 7) @@ -4584,6 +4592,8 @@ def main(): settings = MisraSettings(args) checker = MisraChecker(settings) + checker.path_premium_addon = cppcheckdata.get_path_premium_addon() + if args.generate_table: generateTable() sys.exit(0) @@ -4645,6 +4655,13 @@ def main(): checker.analyse_ctu_info(ctu_info_files) + if args.file_list and checker.path_premium_addon: + premium_command = [checker.path_premium_addon, '--misra', '--file-list', args.file_list] + if args.cli: + premium_command.append('--cli') + for line in subprocess.check_output(premium_command).decode('ascii').split('\n'): + print(line.strip()) + if settings.verify: sys.exit(exitCode)