Add --suppress-rules argument for misra.py (#1259)

* Added ignore list

- Added ignore list
- Use argparse to parse arguments
- Source formatting

* misra.py changed argument name

- Changed ` --ignore-rules` to ` --suppress-rules`

* Resolved VERIFY option in misra.py

Resolved broken broken logic in commit to add argparse.

* edit suppression list

Modified names
Hide some arguments

* command line help formatting

* Revert removal of --no-summary argument

* missing text rules no longer reported

* VERIFY scope modified to global
This commit is contained in:
Kyle Chisholm 2018-05-24 00:31:20 -04:00 committed by Daniel Marjamäki
parent 9f47d04af6
commit 6ce3571bdd
1 changed files with 158 additions and 102 deletions

View File

@ -19,25 +19,38 @@ import cppcheckdata
import sys
import re
import os
import tempfile
import subprocess
import argparse
ruleTexts = {}
suppressRules = {}
typeBits = {
'CHAR': None,
'SHORT': None,
'INT': None,
'LONG': None,
'LONG_LONG': None,
'POINTER': None
}
VERIFY = False
SHOW_SUMMARY = True
QUIET = False
SHOW_SUMMARY = True
VERIFY_EXPECTED = []
VERIFY_ACTUAL = []
VIOLATIONS = []
def printStatus(*args, **kwargs):
if not QUIET:
print(*args, **kwargs)
def reportError(location, num1, num2):
if VERIFY:
VERIFY_ACTUAL.append(str(location.linenr) + ':' + str(num1) + '.' + str(num2))
elif num1 in suppressRules and num2 in suppressRules[num1]:
# ignore error
return
else:
num = num1 * 100 + num2
id = 'misra-c2012-' + str(num1) + '.' + str(num2)
@ -47,11 +60,11 @@ def reportError(location, num1, num2):
errmsg = 'misra violation (use --rule-texts=<file> to get proper output) [' + id + ']'
else:
return
errmsg = '[' + location.file + ':' + str(location.linenr) + '] (style): ' + errmsg + '\n'
sys.stderr.write(errmsg)
sys.stderr.write('[' + location.file + ':' + str(location.linenr) + '] (style): ' + errmsg + '\n')
VIOLATIONS.append(errmsg)
def simpleMatch(token, pattern):
for p in pattern.split(' '):
if not token or token.str != p:
@ -59,6 +72,7 @@ def simpleMatch(token, pattern):
token = token.next
return True
def rawlink(rawtoken):
if rawtoken.str == '}':
indent = 0
@ -74,6 +88,7 @@ def rawlink(rawtoken):
rawtoken = None
return rawtoken
KEYWORDS = {
'auto',
'break',
@ -178,15 +193,15 @@ def bitsOfEssentialType(expr):
if type is None:
return 0
if type == 'char':
return CHAR_BIT
return typeBits['CHAR']
if type == 'short':
return SHORT_BIT
return typeBits['SHORT']
if type == 'int':
return INT_BIT
return typeBits['INT']
if type == 'long':
return LONG_BIT
return typeBits['LONG']
if type == 'long long':
return LONG_LONG_BIT
return typeBits['LONG_LONG']
return 0
@ -356,7 +371,7 @@ def findRawLink(token):
if indent <= 1:
return token
indent = indent - 1
if forward == True:
if forward is True:
token = token.next
else:
token = token.previous
@ -364,6 +379,7 @@ def findRawLink(token):
# raw link not found
return None
def numberOfParentheses(tok1, tok2):
while tok1 and tok1 != tok2:
if tok1.str == '(' or tok1.str == ')':
@ -399,7 +415,8 @@ def getArgumentsRecursive(tok, arguments):
getArgumentsRecursive(tok.astOperand1, arguments)
getArgumentsRecursive(tok.astOperand2, arguments)
else:
arguments.append(tok);
arguments.append(tok)
def getArguments(ftok):
arguments = []
@ -410,9 +427,11 @@ def getArguments(ftok):
def isHexDigit(c):
return (c >= '0' and c <= '9') or (c >= 'a' and c <= 'f') or (c >= 'A' and c >= 'F')
def isOctalDigit(c):
return (c >= '0' and c <= '7')
def isNoReturnScope(tok):
if tok is None or tok.str != '}':
return False
@ -429,12 +448,14 @@ def isNoReturnScope(tok):
return True
return False
def misra_3_1(rawTokens):
for token in rawTokens:
if token.str.startswith('/*') or token.str.startswith('//'):
if '//' in token.str[2:] or '/*' in token.str[2:]:
reportError(token, 3, 1)
def misra_4_1(rawTokens):
for token in rawTokens:
if token.str[0] != '"':
@ -489,6 +510,7 @@ def misra_5_1(data):
else:
reportError(variable2.nameToken, 5, 1)
def misra_5_2(data):
scopeVars = {}
for var in data.variables:
@ -534,7 +556,6 @@ def misra_5_2(data):
reportError(scopename2.bodyStart, 5, 2)
def misra_5_3(data):
enum = []
scopeVars = {}
@ -587,6 +608,7 @@ def misra_5_3(data):
if (scope.className and scope.className[:31] == e[:31]):
reportError(scope.bodyStart, 5, 3)
def misra_5_4(data):
macro = {}
compile_name = re.compile(r'#define ([a-zA-Z0-9_]+)')
@ -986,7 +1008,8 @@ def misra_12_2(data):
def misra_12_3(data):
for token in data.tokenlist:
if token.str != ',' or token.scope.type == 'Enum' or token.scope.type == 'Class' or token.scope.type == 'Global':
if token.str != ',' or token.scope.type == 'Enum' or \
token.scope.type == 'Class' or token.scope.type == 'Global':
continue
if token.astParent and token.astParent.str in ['(', ',', '{']:
continue
@ -994,9 +1017,9 @@ def misra_12_3(data):
def misra_12_4(data):
if INT_BIT == 16:
if typeBits['INT'] == 16:
max_uint = 0xffff
elif INT_BIT == 32:
elif typeBits['INT'] == 32:
max_uint = 0xffffffff
else:
return
@ -1392,6 +1415,7 @@ def misra_20_5(data):
if directive.str.startswith('#undef '):
reportError(directive, 20, 5)
def misra_20_13(data):
for directive in data.directives:
dir = directive.str
@ -1399,9 +1423,11 @@ def misra_20_13(data):
dir = dir[:dir.find(' ')]
if dir.find('(') > 0:
dir = dir[:dir.find('(')]
if dir not in ['#define', '#elif', '#else', '#endif', '#error', '#if', '#ifdef', '#ifndef', '#include', '#pragma', '#undef', '#warning']:
if dir not in ['#define', '#elif', '#else', '#endif', '#error', '#if', '#ifdef', '#ifndef', '#include',
'#pragma', '#undef', '#warning']:
reportError(directive, 20, 13)
def misra_20_14(data):
# stack for #if blocks. contains the #if directive until the corresponding #endif is seen.
# the size increases when there are inner #if directives.
@ -1422,6 +1448,7 @@ def misra_20_14(data):
reportError(directive, 20, 14)
ifStack.pop()
def misra_21_3(data):
for token in data.tokenlist:
if isFunctionCall(token) and (token.astOperand1.str in {'malloc', 'calloc', 'realloc', 'free'}):
@ -1483,6 +1510,26 @@ def misra_21_11(data):
reportError(directive, 21, 11)
def setSuppressionList(suppressionlist):
num1 = 0
num2 = 0
global suppressRules
rule_pattern = re.compile(r'([0-9]+).([0-9]+)')
strlist = suppressionlist.split(",")
# build ignore list
suppressRules = {}
for item in strlist:
res = rule_pattern.match(item)
if res:
num1 = int(res.group(1))
num2 = int(res.group(2))
if num1 in suppressRules:
suppressRules[num1][num2] = True
else:
suppressRules[num1] = {num2: True}
def loadRuleTexts(filename):
num1 = 0
num2 = 0
@ -1526,37 +1573,6 @@ def loadRuleTexts(filename):
ruleTexts[num] = ruleTexts[num] + ' ' + line
continue
if len(sys.argv) == 1:
print("""
Syntax: misra.py [OPTIONS] <dumpfiles>
OPTIONS:
--rule-texts=<file> Load rule texts from plain text file.
If you have the tool 'pdftotext' you might be able
to generate this textfile with such command:
$ pdftotext MISRA_C_2012.pdf MISRA_C_2012.txt
Otherwise you can more or less copy/paste the chapter
Appendix A Summary of guidelines
from the MISRA pdf. You can buy the MISRA pdf from
http://www.misra.org.uk/
Format:
<..arbitrary text..>
Appendix A Summary of guidelines
Rule 1.1
Rule text for 1.1
Rule 1.2
Rule text for 1.2
<...>
--quiet Only print something when there is an error
""")
sys.exit(1)
def generateTable():
numberOfRules = {}
@ -1593,10 +1609,11 @@ def generateTable():
addon.append(res.group(1) + '.' + res.group(2))
# rules handled by cppcheck
cppcheck = ['1.3', '2.1', '2.2', '2.4', '2.6', '8.3', '12.2', '13.2', '13.6', '17.5', '18.1', '18.6', '20.6', '22.1', '22.2', '22.4', '22.6']
cppcheck = ['1.3', '2.1', '2.2', '2.4', '2.6', '8.3', '12.2', '13.2', '13.6', '17.5', '18.1', '18.6',
'20.6', '22.1', '22.2', '22.4', '22.6']
# rules that can be checked with compilers
compiler = ['1.1', '1.2']
# compiler = ['1.1', '1.2']
# print table
for i1 in range(1, 23):
@ -1611,44 +1628,19 @@ def generateTable():
print(num[:8] + s)
sys.exit(1)
for arg in sys.argv[1:]:
if arg == '-verify':
VERIFY = True
elif arg.startswith('--rule-texts='):
filename = arg[13:]
if not os.path.isfile(filename):
print('Fatal error: file is not found: ' + filename)
sys.exit(1)
loadRuleTexts(filename)
elif ".dump" in arg:
continue
elif arg == "-generate-table":
generateTable()
elif arg == "--no-summary":
SHOW_SUMMARY = False
elif arg == "--quiet":
QUIET = True
else:
print('Fatal error: unhandled argument ' + arg)
sys.exit(1)
exitCode = 0
for arg in sys.argv[1:]:
if not arg.endswith('.dump'):
continue
def parseDump(dumpfile):
data = cppcheckdata.parsedump(arg)
data = cppcheckdata.parsedump(dumpfile)
CHAR_BIT = data.platform.char_bit
SHORT_BIT = data.platform.short_bit
INT_BIT = data.platform.int_bit
LONG_BIT = data.platform.long_bit
LONG_LONG_BIT = data.platform.long_long_bit
POINTER_BIT = data.platform.pointer_bit
typeBits['CHAR'] = data.platform.char_bit
typeBits['SHORT'] = data.platform.short_bit
typeBits['INT'] = data.platform.int_bit
typeBits['LONG'] = data.platform.long_bit
typeBits['LONG_LONG'] = data.platform.long_long_bit
typeBits['POINTER'] = data.platform.pointer_bit
if VERIFY:
VERIFY_ACTUAL = []
VERIFY_EXPECTED = []
for tok in data.rawTokens:
if tok.str.startswith('//') and 'TODO' not in tok.str:
compiled = re.compile(r'[0-9]+\.[0-9]+')
@ -1656,14 +1648,14 @@ for arg in sys.argv[1:]:
if compiled.match(word):
VERIFY_EXPECTED.append(str(tok.linenr) + ':' + word)
else:
printStatus('Checking ' + arg + '...')
printStatus('Checking ' + dumpfile + '...')
cfgNumber = 0
for cfg in data.configurations:
cfgNumber = cfgNumber + 1
if len(data.configurations) > 1:
printStatus('Checking ' + arg + ', config "' + cfg.name + '"...')
printStatus('Checking ' + dumpfile + ', config "' + cfg.name + '"...')
if cfgNumber == 1:
misra_3_1(data.rawTokens)
@ -1747,8 +1739,9 @@ for arg in sys.argv[1:]:
misra_21_10(cfg)
misra_21_11(cfg)
# 22.4 is already covered by Cppcheck writeReadOnlyFile
if VERIFY:
exitCode = 0
if VERIFY:
for expected in VERIFY_EXPECTED:
if expected not in VERIFY_ACTUAL:
print('Expected but not seen: ' + expected)
@ -1757,11 +1750,74 @@ for arg in sys.argv[1:]:
if actual not in VERIFY_EXPECTED:
print('Not expected: ' + actual)
exitCode = 1
if not VERIFY:
else:
if len(VIOLATIONS) > 0:
if SHOW_SUMMARY:
print("\nRule violations found: %d\n" % (len(VIOLATIONS)))
exitCode = 1
sys.exit(exitCode)
RULE_TEXTS_HELP = '''Path to text file of MISRA rules
If you have the tool 'pdftotext' you might be able
to generate this textfile with such command:
pdftotext MISRA_C_2012.pdf MISRA_C_2012.txt
Otherwise you can more or less copy/paste the chapter
Appendix A Summary of guidelines
from the MISRA pdf. You can buy the MISRA pdf from
http://www.misra.org.uk/
Format:
<..arbitrary text..>
Appendix A Summary of guidelines
Rule 1.1
Rule text for 1.1
Rule 1.2
Rule text for 1.2
<...>
'''
SUPPRESS_RULES_HELP = '''MISRA rules to suppress (comma-separated)
For example, if you'd like to suppress rules 15.1, 11.3,
and 20.13, run:
python misra.py --suppress-rules 15.1,11.3,20.13 ...
'''
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("--rule-texts", type=str, help=RULE_TEXTS_HELP)
parser.add_argument("--suppress-rules", type=str, help=SUPPRESS_RULES_HELP)
parser.add_argument("--quiet", help="Only print something when there is an error", action="store_true")
parser.add_argument("--no-summary", help="Hide summary of violations", action="store_true")
parser.add_argument("-verify", help=argparse.SUPPRESS, action="store_true")
parser.add_argument("-generate-table", help=argparse.SUPPRESS, action="store_true")
parser.add_argument("file", help="Path of dump file from cppcheck")
args = parser.parse_args()
if args.generate_table:
generateTable()
else:
if args.verify:
VERIFY = True
if args.rule_texts:
filename = os.path.normpath(args.rule_texts)
if not os.path.isfile(filename):
print('Fatal error: file is not found: ' + filename)
sys.exit(1)
loadRuleTexts(filename)
if args.suppress_rules:
setSuppressionList(args.suppress_rules)
if args.quiet:
QUIET = True
if args.no_summary:
SHOW_SUMMARY = False
if args.file:
parseDump(args.file)