1900 lines
60 KiB
Python
Executable File
1900 lines
60 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# MISRA C 2012 checkers
|
|
#
|
|
# Example usage of this addon (scan a sourcefile main.cpp)
|
|
# cppcheck --dump main.cpp
|
|
# python misra.py --rule-texts=<path-to-rule-texts> main.cpp.dump
|
|
#
|
|
# Limitations: This addon is released as open source. Rule texts can't be freely
|
|
# distributed. https://www.misra.org.uk/forum/viewtopic.php?f=56&t=1189
|
|
#
|
|
# The MISRA standard documents may be obtained from https://www.misra.org.uk
|
|
#
|
|
# Total number of rules: 143
|
|
|
|
from __future__ import print_function
|
|
|
|
import cppcheckdata
|
|
import sys
|
|
import re
|
|
import os
|
|
import argparse
|
|
|
|
ruleTexts = {}
|
|
suppressRules = {}
|
|
suppressions = None
|
|
typeBits = {
|
|
'CHAR': None,
|
|
'SHORT': None,
|
|
'INT': None,
|
|
'LONG': None,
|
|
'LONG_LONG': None,
|
|
'POINTER': None
|
|
}
|
|
|
|
VERIFY = False
|
|
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)
|
|
if num in ruleTexts:
|
|
errmsg = ruleTexts[num] + ' [' + id + ']'
|
|
elif len(ruleTexts) == 0:
|
|
errmsg = 'misra violation (use --rule-texts=<file> to get proper output) [' + id + ']'
|
|
else:
|
|
return
|
|
if not cppcheckdata.reportError('[{file}:{line}] ({severity}): {message}',
|
|
callstack=[(location.file, location.linenr)],
|
|
severity='style',
|
|
message = errmsg + '\n',
|
|
errorId = id,
|
|
suppressions = suppressions,
|
|
outputFunc = sys.stderr.write) is None:
|
|
VIOLATIONS.append(errmsg)
|
|
|
|
|
|
def simpleMatch(token, pattern):
|
|
for p in pattern.split(' '):
|
|
if not token or token.str != p:
|
|
return False
|
|
token = token.next
|
|
return True
|
|
|
|
|
|
def rawlink(rawtoken):
|
|
if rawtoken.str == '}':
|
|
indent = 0
|
|
while rawtoken:
|
|
if rawtoken.str == '}':
|
|
indent = indent + 1
|
|
elif rawtoken.str == '{':
|
|
indent = indent - 1
|
|
if indent == 0:
|
|
break
|
|
rawtoken = rawtoken.previous
|
|
else:
|
|
rawtoken = None
|
|
return rawtoken
|
|
|
|
|
|
KEYWORDS = {
|
|
'auto',
|
|
'break',
|
|
'case',
|
|
'char',
|
|
'const',
|
|
'continue',
|
|
'default',
|
|
'do',
|
|
'double',
|
|
'else',
|
|
'enum',
|
|
'extern',
|
|
'float',
|
|
'for',
|
|
'goto',
|
|
'if',
|
|
'int',
|
|
'long',
|
|
'register',
|
|
'return',
|
|
'short',
|
|
'signed',
|
|
'sizeof',
|
|
'static',
|
|
'struct',
|
|
'switch',
|
|
'typedef',
|
|
'union',
|
|
'unsigned',
|
|
'void',
|
|
'volatile',
|
|
'while'
|
|
}
|
|
|
|
|
|
def getEssentialTypeCategory(expr):
|
|
if not expr:
|
|
return None
|
|
if expr.valueType and expr.valueType.typeScope:
|
|
return "enum<" + expr.valueType.typeScope.className + ">"
|
|
if expr.variable:
|
|
typeToken = expr.variable.typeStartToken
|
|
while typeToken:
|
|
if typeToken.valueType:
|
|
if typeToken.valueType.type in {'bool'}:
|
|
return typeToken.valueType.type
|
|
if typeToken.valueType.type in {'float', 'double', 'long double'}:
|
|
return "float"
|
|
if typeToken.valueType.sign:
|
|
return typeToken.valueType.sign
|
|
typeToken = typeToken.next
|
|
if expr.valueType:
|
|
return expr.valueType.sign
|
|
return None
|
|
|
|
|
|
def getEssentialCategorylist(operand1, operand2):
|
|
if not operand1 or not operand2:
|
|
return None, None
|
|
if (operand1.str in {'++', '--'} or
|
|
operand2.str in {'++', '--'}):
|
|
return None, None
|
|
if (operand1.valueType.pointer or
|
|
operand2.valueType.pointer):
|
|
return None, None
|
|
e1 = getEssentialTypeCategory(operand1)
|
|
e2 = getEssentialTypeCategory(operand2)
|
|
return e1, e2
|
|
|
|
|
|
def getEssentialType(expr):
|
|
if not expr:
|
|
return None
|
|
if expr.variable:
|
|
typeToken = expr.variable.typeStartToken
|
|
while typeToken and typeToken.isName:
|
|
if typeToken.str in {'char', 'short', 'int', 'long', 'float', 'double'}:
|
|
return typeToken.str
|
|
typeToken = typeToken.next
|
|
|
|
elif expr.astOperand1 and expr.astOperand2 and expr.str in {'+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":"}:
|
|
if expr.astOperand1.valueType and expr.astOperand1.valueType.pointer > 0:
|
|
return None
|
|
if expr.astOperand2.valueType and expr.astOperand2.valueType.pointer > 0:
|
|
return None
|
|
e1 = getEssentialType(expr.astOperand1)
|
|
e2 = getEssentialType(expr.astOperand2)
|
|
if not e1 or not e2:
|
|
return None
|
|
types = ['bool', 'char', 'short', 'int', 'long', 'long long']
|
|
try:
|
|
i1 = types.index(e1)
|
|
i2 = types.index(e2)
|
|
if i2 >= i1:
|
|
return types[i2]
|
|
return types[i1]
|
|
except ValueError:
|
|
return None
|
|
elif expr.str == "~":
|
|
e1 = getEssentialType(expr.astOperand1)
|
|
return e1
|
|
|
|
return None
|
|
|
|
|
|
def bitsOfEssentialType(expr):
|
|
type = getEssentialType(expr)
|
|
if type is None:
|
|
return 0
|
|
if type == 'char':
|
|
return typeBits['CHAR']
|
|
if type == 'short':
|
|
return typeBits['SHORT']
|
|
if type == 'int':
|
|
return typeBits['INT']
|
|
if type == 'long':
|
|
return typeBits['LONG']
|
|
if type == 'long long':
|
|
return typeBits['LONG_LONG']
|
|
return 0
|
|
|
|
|
|
def isCast(expr):
|
|
if not expr or expr.str != '(' or not expr.astOperand1 or expr.astOperand2:
|
|
return False
|
|
if simpleMatch(expr, '( )'):
|
|
return False
|
|
return True
|
|
|
|
|
|
def isFunctionCall(expr):
|
|
if not expr:
|
|
return False
|
|
if expr.str != '(' or not expr.astOperand1:
|
|
return False
|
|
if expr.astOperand1 != expr.previous:
|
|
return False
|
|
if expr.astOperand1.str in KEYWORDS:
|
|
return False
|
|
return True
|
|
|
|
|
|
def countSideEffects(expr):
|
|
if not expr or expr.str in {',', ';'}:
|
|
return 0
|
|
ret = 0
|
|
if expr.str in {'++', '--', '='}:
|
|
ret = 1
|
|
return ret + countSideEffects(expr.astOperand1) + countSideEffects(expr.astOperand2)
|
|
|
|
|
|
def getForLoopExpressions(forToken):
|
|
if not forToken or forToken.str != 'for':
|
|
return None
|
|
lpar = forToken.next
|
|
if not lpar or lpar.str != '(':
|
|
return None
|
|
if not lpar.astOperand2 or lpar.astOperand2.str != ';':
|
|
return None
|
|
if not lpar.astOperand2.astOperand2 or lpar.astOperand2.astOperand2.str != ';':
|
|
return None
|
|
return [lpar.astOperand2.astOperand1,
|
|
lpar.astOperand2.astOperand2.astOperand1,
|
|
lpar.astOperand2.astOperand2.astOperand2]
|
|
|
|
|
|
def findCounterTokens(cond):
|
|
if not cond:
|
|
return []
|
|
if cond.str in ['&&', '||']:
|
|
c = findCounterTokens(cond.astOperand1)
|
|
c.extend(findCounterTokens(cond.astOperand2))
|
|
return c
|
|
ret = []
|
|
if ((cond.isArithmeticalOp and cond.astOperand1 and cond.astOperand2) or
|
|
(cond.isComparisonOp and cond.astOperand1 and cond.astOperand2)):
|
|
if cond.astOperand1.isName:
|
|
ret.append(cond.astOperand1)
|
|
if cond.astOperand2.isName:
|
|
ret.append(cond.astOperand2)
|
|
if cond.astOperand1.isOp:
|
|
ret.extend(findCounterTokens(cond.astOperand1))
|
|
if cond.astOperand2.isOp:
|
|
ret.extend(findCounterTokens(cond.astOperand2))
|
|
return ret
|
|
|
|
|
|
def isFloatCounterInWhileLoop(whileToken):
|
|
if not simpleMatch(whileToken, 'while ('):
|
|
return False
|
|
lpar = whileToken.next
|
|
rpar = lpar.link
|
|
counterTokens = findCounterTokens(lpar.astOperand2)
|
|
whileBodyStart = None
|
|
if simpleMatch(rpar, ') {'):
|
|
whileBodyStart = rpar.next
|
|
elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {'):
|
|
whileBodyStart = whileToken.previous.link
|
|
else:
|
|
return False
|
|
token = whileBodyStart
|
|
while (token != whileBodyStart.link):
|
|
token = token.next
|
|
for counterToken in counterTokens:
|
|
if not counterToken.valueType or not counterToken.valueType.isFloat():
|
|
continue
|
|
if token.isAssignmentOp and token.astOperand1.str == counterToken.str:
|
|
return True
|
|
if token.str == counterToken.str and token.astParent and token.astParent.str in {'++', '--'}:
|
|
return True
|
|
return False
|
|
|
|
|
|
def hasSideEffectsRecursive(expr):
|
|
if not expr:
|
|
return False
|
|
if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '[':
|
|
prev = expr.astOperand1.previous
|
|
if prev and (prev.str == '{' or prev.str == '{'):
|
|
return hasSideEffectsRecursive(expr.astOperand2)
|
|
if expr.str == '=' and expr.astOperand1 and expr.astOperand1.str == '.':
|
|
e = expr.astOperand1
|
|
while e and e.str == '.' and e.astOperand2:
|
|
e = e.astOperand1
|
|
if e and e.str == '.':
|
|
return False
|
|
if expr.str in {'++', '--', '='}:
|
|
return True
|
|
# Todo: Check function calls
|
|
return hasSideEffectsRecursive(expr.astOperand1) or hasSideEffectsRecursive(expr.astOperand2)
|
|
|
|
|
|
def isBoolExpression(expr):
|
|
if not expr:
|
|
return False
|
|
if expr.valueType and (expr.valueType.type == 'bool' or expr.valueType.bits == 1):
|
|
return True
|
|
return expr.str in ['!', '==', '!=', '<', '<=', '>', '>=', '&&', '||', '0', '1', 'true', 'false']
|
|
|
|
|
|
def isConstantExpression(expr):
|
|
if expr.isNumber:
|
|
return True
|
|
if expr.isName:
|
|
return False
|
|
if simpleMatch(expr.previous, 'sizeof ('):
|
|
return True
|
|
if expr.astOperand1 and not isConstantExpression(expr.astOperand1):
|
|
return False
|
|
if expr.astOperand2 and not isConstantExpression(expr.astOperand2):
|
|
return False
|
|
return True
|
|
|
|
|
|
def isUnsignedInt(expr):
|
|
# TODO this function is very incomplete. use ValueType?
|
|
if not expr:
|
|
return False
|
|
if expr.isNumber:
|
|
return 'u' in expr.str or 'U' in expr.str
|
|
if expr.str in {'+', '-', '*', '/', '%'}:
|
|
return isUnsignedInt(expr.astOperand1) or isUnsignedInt(expr.astOperand2)
|
|
return False
|
|
|
|
|
|
def getPrecedence(expr):
|
|
if not expr:
|
|
return 16
|
|
if not expr.astOperand1 or not expr.astOperand2:
|
|
return 16
|
|
if expr.str in {'*', '/', '%'}:
|
|
return 12
|
|
if expr.str in {'+', '-'}:
|
|
return 11
|
|
if expr.str in {'<<', '>>'}:
|
|
return 10
|
|
if expr.str in {'<', '>', '<=', '>='}:
|
|
return 9
|
|
if expr.str in {'==', '!='}:
|
|
return 8
|
|
if expr.str == '&':
|
|
return 7
|
|
if expr.str == '^':
|
|
return 6
|
|
if expr.str == '|':
|
|
return 5
|
|
if expr.str == '&&':
|
|
return 4
|
|
if expr.str == '||':
|
|
return 3
|
|
if expr.str in {'?', ':'}:
|
|
return 2
|
|
if expr.isAssignmentOp:
|
|
return 1
|
|
if expr.str == ',':
|
|
return 0
|
|
return -1
|
|
|
|
|
|
def findRawLink(token):
|
|
tok1 = None
|
|
tok2 = None
|
|
forward = False
|
|
|
|
if token.str in '{([':
|
|
tok1 = token.str
|
|
tok2 = '})]'['{(['.find(token.str)]
|
|
forward = True
|
|
elif token.str in '})]':
|
|
tok1 = token.str
|
|
tok2 = '{(['['})]'.find(token.str)]
|
|
forward = False
|
|
else:
|
|
return None
|
|
|
|
# try to find link
|
|
indent = 0
|
|
while token:
|
|
if token.str == tok1:
|
|
indent = indent + 1
|
|
elif token.str == tok2:
|
|
if indent <= 1:
|
|
return token
|
|
indent = indent - 1
|
|
if forward is True:
|
|
token = token.next
|
|
else:
|
|
token = token.previous
|
|
|
|
# raw link not found
|
|
return None
|
|
|
|
|
|
def numberOfParentheses(tok1, tok2):
|
|
while tok1 and tok1 != tok2:
|
|
if tok1.str == '(' or tok1.str == ')':
|
|
return False
|
|
tok1 = tok1.next
|
|
return tok1 == tok2
|
|
|
|
|
|
def findGotoLabel(gotoToken):
|
|
label = gotoToken.next.str
|
|
tok = gotoToken.next.next
|
|
while tok:
|
|
if tok.str == '}' and tok.scope.type == 'Function':
|
|
break
|
|
if tok.str == label and tok.next.str == ':':
|
|
return tok
|
|
tok = tok.next
|
|
return None
|
|
|
|
|
|
def findInclude(directives, header):
|
|
for directive in directives:
|
|
if directive.str == '#include ' + header:
|
|
return directive
|
|
return None
|
|
|
|
|
|
# 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):
|
|
arguments = []
|
|
getArgumentsRecursive(ftok.astOperand2, arguments)
|
|
return arguments
|
|
|
|
|
|
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
|
|
if tok.previous is None or tok.previous.str != ';':
|
|
return False
|
|
if simpleMatch(tok.previous.previous, 'break ;'):
|
|
return True
|
|
prev = tok.previous.previous
|
|
while prev and prev.str not in ';{}':
|
|
if prev.str in '])':
|
|
prev = prev.link
|
|
prev = prev.previous
|
|
if prev and prev.next.str in ['throw', 'return']:
|
|
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] != '"':
|
|
continue
|
|
pos = 1
|
|
while pos < len(token.str) - 2:
|
|
pos1 = pos
|
|
pos = pos + 1
|
|
if token.str[pos1] != '\\':
|
|
continue
|
|
if token.str[pos1 + 1] == '\\':
|
|
pos = pos1 + 2
|
|
continue
|
|
if token.str[pos1 + 1] == 'x':
|
|
if not isHexDigit(token.str[pos1 + 2]):
|
|
reportError(token, 4, 1)
|
|
continue
|
|
if not isHexDigit(token.str[pos1 + 3]):
|
|
reportError(token, 4, 1)
|
|
continue
|
|
elif isOctalDigit(token.str[pos1 + 1]):
|
|
if not isOctalDigit(token.str[pos1 + 2]):
|
|
reportError(token, 4, 1)
|
|
continue
|
|
if not isOctalDigit(token.str[pos1 + 2]):
|
|
reportError(token, 4, 1)
|
|
continue
|
|
else:
|
|
continue
|
|
|
|
c = token.str[pos1 + 4]
|
|
if c != '"' and c != '\\':
|
|
reportError(token, 4, 1)
|
|
|
|
|
|
def misra_5_1(data):
|
|
scopeVars = {}
|
|
for var in data.variables:
|
|
if var.isArgument:
|
|
continue
|
|
if var.nameToken.scope not in scopeVars:
|
|
scopeVars[var.nameToken.scope] = []
|
|
scopeVars[var.nameToken.scope].append(var)
|
|
for scope in scopeVars:
|
|
for i, variable1 in enumerate(scopeVars[scope]):
|
|
for variable2 in scopeVars[scope][i + 1:]:
|
|
if (variable1.isExtern and variable2.isExtern and
|
|
variable1.nameToken.str[:31] == variable2.nameToken.str[:31] and
|
|
variable1.Id != variable2.Id):
|
|
if int(variable1.nameToken.linenr) > int(variable2.nameToken.linenr):
|
|
reportError(variable1.nameToken, 5, 1)
|
|
else:
|
|
reportError(variable2.nameToken, 5, 1)
|
|
|
|
|
|
def misra_5_2(data):
|
|
scopeVars = {}
|
|
for var in data.variables:
|
|
if var.nameToken.scope not in scopeVars:
|
|
scopeVars.setdefault(var.nameToken.scope, {})["varlist"] = []
|
|
scopeVars.setdefault(var.nameToken.scope, {})["scopelist"] = []
|
|
scopeVars[var.nameToken.scope]["varlist"].append(var)
|
|
for scope in data.scopes:
|
|
if scope.nestedIn and scope.className:
|
|
if scope.nestedIn not in scopeVars:
|
|
scopeVars.setdefault(scope.nestedIn, {})["varlist"] = []
|
|
scopeVars.setdefault(scope.nestedIn, {})["scopelist"] = []
|
|
scopeVars[scope.nestedIn]["scopelist"].append(scope)
|
|
for scope in scopeVars:
|
|
if len(scopeVars[scope]["varlist"]) <= 1:
|
|
continue
|
|
for i, variable1 in enumerate(scopeVars[scope]["varlist"]):
|
|
for variable2 in scopeVars[scope]["varlist"][i + 1:]:
|
|
if variable1.isArgument and variable2.isArgument:
|
|
continue
|
|
if variable1.isExtern and variable2.isExtern:
|
|
continue
|
|
if (variable1.nameToken.str[:31] == variable2.nameToken.str[:31] and
|
|
variable1.Id != variable2.Id):
|
|
if int(variable1.nameToken.linenr) > int(variable2.nameToken.linenr):
|
|
reportError(variable1.nameToken, 5, 2)
|
|
else:
|
|
reportError(variable2.nameToken, 5, 2)
|
|
for innerscope in scopeVars[scope]["scopelist"]:
|
|
if (variable1.nameToken.str[:31] == innerscope.className[:31]):
|
|
if int(variable1.nameToken.linenr) > int(innerscope.bodyStart.linenr):
|
|
reportError(variable1.nameToken, 5, 2)
|
|
else:
|
|
reportError(innerscope.bodyStart, 5, 2)
|
|
if len(scopeVars[scope]["scopelist"]) <= 1:
|
|
continue
|
|
for i, scopename1 in enumerate(scopeVars[scope]["scopelist"]):
|
|
for scopename2 in scopeVars[scope]["scopelist"][i + 1:]:
|
|
if (scopename1.className[:31] == scopename2.className[:31]):
|
|
if int(scopename1.bodyStart.linenr) > int(scopename2.bodyStart.linenr):
|
|
reportError(scopename1.bodyStart, 5, 2)
|
|
else:
|
|
reportError(scopename2.bodyStart, 5, 2)
|
|
|
|
|
|
def misra_5_3(data):
|
|
enum = []
|
|
scopeVars = {}
|
|
for var in data.variables:
|
|
if var.nameToken.scope not in scopeVars:
|
|
scopeVars[var.nameToken.scope] = []
|
|
scopeVars[var.nameToken.scope].append(var)
|
|
for innerScope in data.scopes:
|
|
if innerScope.type == "Enum":
|
|
enum_token = innerScope.bodyStart.next
|
|
while enum_token != innerScope.bodyEnd:
|
|
if enum_token.values and enum_token.isName:
|
|
enum.append(enum_token.str)
|
|
enum_token = enum_token.next
|
|
continue
|
|
if innerScope not in scopeVars:
|
|
continue
|
|
if innerScope.type == "Global":
|
|
continue
|
|
for innerVar in scopeVars[innerScope]:
|
|
outerScope = innerScope.nestedIn
|
|
while outerScope:
|
|
if outerScope not in scopeVars:
|
|
outerScope = outerScope.nestedIn
|
|
continue
|
|
for outerVar in scopeVars[outerScope]:
|
|
if innerVar.nameToken.str[:31] == outerVar.nameToken.str[:31]:
|
|
if outerVar.isArgument and outerScope.type == "Global" and not innerVar.isArgument:
|
|
continue
|
|
if int(innerVar.nameToken.linenr) > int(outerVar.nameToken.linenr):
|
|
reportError(innerVar.nameToken, 5, 3)
|
|
else:
|
|
reportError(outerVar.nameToken, 5, 3)
|
|
outerScope = outerScope.nestedIn
|
|
for scope in data.scopes:
|
|
if (scope.className and innerVar.nameToken.str[:31] == scope.className[:31]):
|
|
if int(innerVar.nameToken.linenr) > int(scope.bodyStart.linenr):
|
|
reportError(innerVar.nameToken, 5, 3)
|
|
else:
|
|
reportError(scope.bodyStart, 5, 3)
|
|
|
|
for e in enum:
|
|
if scope.className and innerVar.nameToken.str[:31] == e[:31]:
|
|
if int(innerVar.nameToken.linenr) > int(innerScope.bodyStart.linenr):
|
|
reportError(innerVar.nameToken, 5, 3)
|
|
else:
|
|
reportError(innerScope.bodyStart, 5, 3)
|
|
for e in enum:
|
|
for scope in data.scopes:
|
|
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_]+)')
|
|
compile_param = re.compile(r'#define ([a-zA-Z0-9_]+)[\(]([a-zA-Z0-9_, ]+)[\)]')
|
|
for dir in data.directives:
|
|
res1 = compile_name.match(dir.str)
|
|
if res1:
|
|
if dir not in macro:
|
|
macro.setdefault(dir, {})["name"] = []
|
|
macro.setdefault(dir, {})["params"] = []
|
|
macro[dir]["name"] = res1.group(1)
|
|
res2 = compile_param.match(dir.str)
|
|
if res2:
|
|
res_gp2 = res2.group(2).split(",")
|
|
res_gp2 = [macroname.replace(" ", "") for macroname in res_gp2]
|
|
macro[dir]["params"].extend(res_gp2)
|
|
for mvar in macro:
|
|
if len(macro[mvar]["params"]) > 0:
|
|
for i, macroparam1 in enumerate(macro[mvar]["params"]):
|
|
for j, macroparam2 in enumerate(macro[mvar]["params"]):
|
|
if j > i and macroparam1[:31] == macroparam2[:31]:
|
|
reportError(mvar, 5, 4)
|
|
|
|
for x, m_var1 in enumerate(macro):
|
|
for y, m_var2 in enumerate(macro):
|
|
if x < y and macro[m_var1]["name"][:31] == macro[m_var2]["name"][:31]:
|
|
if m_var1.linenr > m_var2.linenr:
|
|
reportError(m_var1, 5, 4)
|
|
else:
|
|
reportError(m_var2, 5, 4)
|
|
for param in macro[m_var2]["params"]:
|
|
if macro[m_var1]["name"][:31] == param[:31]:
|
|
if m_var1.linenr > m_var2.linenr:
|
|
reportError(m_var1, 5, 4)
|
|
else:
|
|
reportError(m_var2, 5, 4)
|
|
|
|
|
|
def misra_5_5(data):
|
|
macroNames = []
|
|
compiled = re.compile(r'#define ([A-Za-z0-9_]+)')
|
|
for dir in data.directives:
|
|
res = compiled.match(dir.str)
|
|
if res:
|
|
macroNames.append(res.group(1))
|
|
for var in data.variables:
|
|
for macro in macroNames:
|
|
if var.nameToken.str[:31] == macro[:31]:
|
|
reportError(var.nameToken, 5, 5)
|
|
for scope in data.scopes:
|
|
for macro in macroNames:
|
|
if scope.className and scope.className[:31] == macro[:31]:
|
|
reportError(scope.bodyStart, 5, 5)
|
|
|
|
|
|
def misra_7_1(rawTokens):
|
|
compiled = re.compile(r'^0[0-7]+$')
|
|
for tok in rawTokens:
|
|
if compiled.match(tok.str):
|
|
reportError(tok, 7, 1)
|
|
|
|
|
|
def misra_7_3(rawTokens):
|
|
compiled = re.compile(r'^[0-9.uU]+l')
|
|
for tok in rawTokens:
|
|
if compiled.match(tok.str):
|
|
reportError(tok, 7, 3)
|
|
|
|
|
|
def misra_8_11(data):
|
|
for var in data.variables:
|
|
if var.isExtern and simpleMatch(var.nameToken.next, '[ ]') and var.nameToken.scope.type == 'Global':
|
|
reportError(var.nameToken, 8, 11)
|
|
|
|
|
|
def misra_8_12(data):
|
|
for scope in data.scopes:
|
|
enum = []
|
|
implicit_enum = []
|
|
if scope.type != 'Enum':
|
|
continue
|
|
e_token = scope.bodyStart.next
|
|
while e_token != scope.bodyEnd:
|
|
if e_token.values:
|
|
enum.append(e_token.str)
|
|
if (e_token.values and e_token.isName and e_token.next.str != "="):
|
|
for v in e_token.values:
|
|
implicit_enum.append(v.intvalue)
|
|
e_token = e_token.next
|
|
for implicit_enum_value in implicit_enum:
|
|
if str(implicit_enum_value) in enum:
|
|
reportError(scope.bodyStart, 8, 12)
|
|
|
|
|
|
def misra_8_14(rawTokens):
|
|
for token in rawTokens:
|
|
if token.str == 'restrict':
|
|
reportError(token, 8, 14)
|
|
|
|
|
|
def misra_9_5(rawTokens):
|
|
for token in rawTokens:
|
|
if simpleMatch(token, '[ ] = { ['):
|
|
reportError(token, 9, 5)
|
|
|
|
|
|
def misra_10_1(data):
|
|
for token in data.tokenlist:
|
|
if not token.isOp:
|
|
continue
|
|
e1 = getEssentialTypeCategory(token.astOperand1)
|
|
e2 = getEssentialTypeCategory(token.astOperand2)
|
|
if not e1 or not e2:
|
|
continue
|
|
if token.str in ['<<', '>>']:
|
|
if e1 != 'unsigned':
|
|
reportError(token, 10, 1)
|
|
elif e2 != 'unsigned' and not token.astOperand2.isNumber:
|
|
reportError(token, 10, 1)
|
|
|
|
def misra_10_4(data):
|
|
op = {'+', '-', '*', '/', '%', '&', '|', '^', '+=', '-=', '?', ':'}
|
|
for token in data.tokenlist:
|
|
if token.str not in op and not token.isComparisonOp:
|
|
continue
|
|
if not token.astOperand1 or not token.astOperand2:
|
|
continue
|
|
if not token.astOperand1.valueType or not token.astOperand2.valueType:
|
|
continue
|
|
if ((token.astOperand1.str in op or token.astOperand1.isComparisonOp) and
|
|
(token.astOperand2.str in op or token.astOperand1.isComparisonOp)):
|
|
e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2.astOperand1)
|
|
elif token.astOperand1.str in op or token.astOperand1.isComparisonOp:
|
|
e1, e2 = getEssentialCategorylist(token.astOperand1.astOperand2, token.astOperand2)
|
|
elif token.astOperand2.str in op or token.astOperand2.isComparisonOp:
|
|
e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2.astOperand1)
|
|
else:
|
|
e1, e2 = getEssentialCategorylist(token.astOperand1, token.astOperand2)
|
|
if token.str == "+=" or token.str == "+":
|
|
if e1 == "char" and (e2 == "signed" or e2 == "unsigned"):
|
|
continue
|
|
if e2 == "char" and (e1 == "signed" or e1 == "unsigned"):
|
|
continue
|
|
if token.str == "-=" or token.str == "-":
|
|
if e1 == "char" and (e2 == "signed" or e2 == "unsigned"):
|
|
continue
|
|
if e1 and e2 and (e1.find('Anonymous') != -1 and (e2 == "signed" or e2 == "unsigned")):
|
|
continue
|
|
if e1 and e2 and (e2.find('Anonymous') != -1 and (e1 == "signed" or e1 == "unsigned")):
|
|
continue
|
|
if e1 and e2 and e1 != e2:
|
|
reportError(token, 10, 4)
|
|
|
|
|
|
def misra_10_6(data):
|
|
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)):
|
|
continue
|
|
vt1 = token.astOperand1.valueType
|
|
vt2 = token.astOperand2.valueType
|
|
if not vt1 or vt1.pointer > 0:
|
|
continue
|
|
if not vt2 or vt2.pointer > 0:
|
|
continue
|
|
try:
|
|
intTypes = ['char', 'short', 'int', 'long', 'long long']
|
|
index1 = intTypes.index(vt1.type)
|
|
if isCast(token.astOperand2):
|
|
e = vt2.type
|
|
else:
|
|
e = getEssentialType(token.astOperand2)
|
|
if not e:
|
|
continue
|
|
index2 = intTypes.index(e)
|
|
if index1 > index2:
|
|
reportError(token, 10, 6)
|
|
except ValueError:
|
|
pass
|
|
|
|
def misra_10_8(data):
|
|
for token in data.tokenlist:
|
|
if not isCast(token):
|
|
continue
|
|
if not token.valueType or token.valueType.pointer > 0:
|
|
continue
|
|
if not token.astOperand1.valueType or token.astOperand1.valueType.pointer > 0:
|
|
continue
|
|
if not token.astOperand1.astOperand1:
|
|
continue
|
|
if token.astOperand1.str not in {'+', '-', '*', '/', '%', '&', '|', '^', '>>', "<<", "?", ":", '~'}:
|
|
continue
|
|
if token.astOperand1.str != '~' and not token.astOperand1.astOperand2:
|
|
continue
|
|
if token.astOperand1.str == '~':
|
|
e2 = getEssentialTypeCategory(token.astOperand1.astOperand1)
|
|
else:
|
|
e2, e3 = getEssentialCategorylist(token.astOperand1.astOperand1, token.astOperand1.astOperand2)
|
|
if e2 != e3:
|
|
continue
|
|
e1 = getEssentialTypeCategory(token)
|
|
if e1 != e2:
|
|
reportError(token, 10, 8)
|
|
else:
|
|
try:
|
|
intTypes = ['char', 'short', 'int', 'long', 'long long']
|
|
index1 = intTypes.index(token.valueType.type)
|
|
e = getEssentialType(token.astOperand1)
|
|
if not e:
|
|
continue
|
|
index2 = intTypes.index(e)
|
|
if index1 > index2:
|
|
reportError(token, 10, 8)
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
def misra_11_3(data):
|
|
for token in data.tokenlist:
|
|
if not isCast(token):
|
|
continue
|
|
vt1 = token.valueType
|
|
vt2 = token.astOperand1.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if vt1.type == 'void' or vt2.type == 'void':
|
|
continue
|
|
if (vt1.pointer > 0 and vt1.type == 'record' and
|
|
vt2.pointer > 0 and vt2.type == 'record' and
|
|
vt1.typeScopeId != vt2.typeScopeId):
|
|
reportError(token, 11, 3)
|
|
elif (vt1.pointer == vt2.pointer and vt1.pointer > 0 and
|
|
vt1.type != vt2.type and vt1.type != 'char'):
|
|
reportError(token, 11, 3)
|
|
|
|
|
|
def misra_11_4(data):
|
|
for token in data.tokenlist:
|
|
if not isCast(token):
|
|
continue
|
|
vt1 = token.valueType
|
|
vt2 = token.astOperand1.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if vt2.pointer > 0 and vt1.pointer == 0 and (vt1.isIntegral() or vt1.isEnum()) and vt2.type != 'void':
|
|
reportError(token, 11, 4)
|
|
elif vt1.pointer > 0 and vt2.pointer == 0 and (vt2.isIntegral() or vt2.isEnum())and vt1.type != 'void':
|
|
reportError(token, 11, 4)
|
|
|
|
|
|
def misra_11_5(data):
|
|
for token in data.tokenlist:
|
|
if not isCast(token):
|
|
if token.astOperand1 and token.astOperand2 and token.str == "=" and token.next.str != "(":
|
|
vt1 = token.astOperand1.valueType
|
|
vt2 = token.astOperand2.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void':
|
|
reportError(token, 11, 5)
|
|
continue
|
|
if token.astOperand1.astOperand1 and token.astOperand1.astOperand1.str in {'malloc', 'calloc', 'realloc', 'free'}:
|
|
continue
|
|
vt1 = token.valueType
|
|
vt2 = token.astOperand1.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if vt1.pointer > 0 and vt1.type != 'void' and vt2.pointer == vt1.pointer and vt2.type == 'void':
|
|
reportError(token, 11, 5)
|
|
|
|
|
|
def misra_11_6(data):
|
|
for token in data.tokenlist:
|
|
if not isCast(token):
|
|
continue
|
|
if token.astOperand1.astOperand1:
|
|
continue
|
|
vt1 = token.valueType
|
|
vt2 = token.astOperand1.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if vt1.pointer == 1 and vt1.type == 'void' and vt2.pointer == 0 and token.astOperand1.str != "0":
|
|
reportError(token, 11, 6)
|
|
elif vt1.pointer == 0 and vt1.type != 'void' and vt2.pointer == 1 and vt2.type == 'void':
|
|
reportError(token, 11, 6)
|
|
|
|
|
|
def misra_11_7(data):
|
|
for token in data.tokenlist:
|
|
if not isCast(token):
|
|
continue
|
|
vt1 = token.valueType
|
|
vt2 = token.astOperand1.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if token.astOperand1.astOperand1:
|
|
continue
|
|
if (vt2.pointer > 0 and vt1.pointer == 0 and
|
|
not vt1.isIntegral() and not vt1.isEnum() and
|
|
vt2.type != 'void'):
|
|
reportError(token, 11, 7)
|
|
elif (vt1.pointer > 0 and vt2.pointer == 0 and
|
|
not vt2.isIntegral() and not vt2.isEnum() and
|
|
vt1.type != 'void'):
|
|
reportError(token, 11, 7)
|
|
|
|
|
|
def misra_11_8(data):
|
|
# TODO: reuse code in CERT-EXP05
|
|
for token in data.tokenlist:
|
|
if isCast(token):
|
|
# C-style cast
|
|
if not token.valueType:
|
|
continue
|
|
if not token.astOperand1.valueType:
|
|
continue
|
|
if token.valueType.pointer == 0:
|
|
continue
|
|
if token.astOperand1.valueType.pointer == 0:
|
|
continue
|
|
const1 = token.valueType.constness
|
|
const2 = token.astOperand1.valueType.constness
|
|
if (const1 % 2) < (const2 % 2):
|
|
reportError(token, 11, 8)
|
|
|
|
elif token.str == '(' and token.astOperand1 and token.astOperand2 and token.astOperand1.function:
|
|
# Function call
|
|
function = token.astOperand1.function
|
|
arguments = getArguments(token)
|
|
for argnr, argvar in function.argument.items():
|
|
if argnr < 1 or argnr > len(arguments):
|
|
continue
|
|
if not argvar.isPointer:
|
|
continue
|
|
argtok = arguments[argnr - 1]
|
|
if not argtok.valueType:
|
|
continue
|
|
if argtok.valueType.pointer == 0:
|
|
continue
|
|
const1 = argvar.isConst
|
|
const2 = arguments[argnr - 1].valueType.constness
|
|
if (const1 % 2) < (const2 % 2):
|
|
reportError(token, 11, 8)
|
|
|
|
|
|
def misra_11_9(data):
|
|
for token in data.tokenlist:
|
|
if token.astOperand1 and token.astOperand2 and token.str in ["=", "==", "!=", "?", ":"]:
|
|
vt1 = token.astOperand1.valueType
|
|
vt2 = token.astOperand2.valueType
|
|
if not vt1 or not vt2:
|
|
continue
|
|
if vt1.pointer > 0 and vt2.pointer == 0 and token.astOperand2.str == "NULL":
|
|
continue
|
|
if (token.astOperand2.values and vt1.pointer > 0 and
|
|
vt2.pointer == 0 and token.astOperand2.values):
|
|
for val in token.astOperand2.values:
|
|
if val.intvalue == 0:
|
|
reportError(token, 11, 9)
|
|
|
|
def misra_12_1_sizeof(rawTokens):
|
|
state = 0
|
|
compiled = re.compile(r'^[a-zA-Z_]')
|
|
for tok in rawTokens:
|
|
if tok.str.startswith('//') or tok.str.startswith('/*'):
|
|
continue
|
|
if tok.str == 'sizeof':
|
|
state = 1
|
|
elif state == 1:
|
|
if compiled.match(tok.str):
|
|
state = 2
|
|
else:
|
|
state = 0
|
|
elif state == 2:
|
|
if tok.str in {'+', '-', '*', '/', '%'}:
|
|
reportError(tok, 12, 1)
|
|
else:
|
|
state = 0
|
|
|
|
|
|
def misra_12_1(data):
|
|
for token in data.tokenlist:
|
|
p = getPrecedence(token)
|
|
if p < 2 or p > 12:
|
|
continue
|
|
p1 = getPrecedence(token.astOperand1)
|
|
if p < p1 <= 12 and numberOfParentheses(token.astOperand1, token):
|
|
reportError(token, 12, 1)
|
|
continue
|
|
p2 = getPrecedence(token.astOperand2)
|
|
if p < p2 <= 12 and numberOfParentheses(token, token.astOperand2):
|
|
reportError(token, 12, 1)
|
|
continue
|
|
|
|
|
|
def misra_12_2(data):
|
|
for token in data.tokenlist:
|
|
if not (token.str in {'<<', '>>'}):
|
|
continue
|
|
if (not token.astOperand2) or (not token.astOperand2.values):
|
|
continue
|
|
maxval = 0
|
|
for val in token.astOperand2.values:
|
|
if val.intvalue and val.intvalue > maxval:
|
|
maxval = val.intvalue
|
|
if maxval == 0:
|
|
continue
|
|
sz = bitsOfEssentialType(token.astOperand1)
|
|
if sz <= 0:
|
|
continue
|
|
if maxval >= sz:
|
|
reportError(token, 12, 2)
|
|
|
|
|
|
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':
|
|
continue
|
|
if token.astParent and token.astParent.str in ['(', ',', '{']:
|
|
continue
|
|
reportError(token, 12, 3)
|
|
|
|
|
|
def misra_12_4(data):
|
|
if typeBits['INT'] == 16:
|
|
max_uint = 0xffff
|
|
elif typeBits['INT'] == 32:
|
|
max_uint = 0xffffffff
|
|
else:
|
|
return
|
|
|
|
for token in data.tokenlist:
|
|
if (not isConstantExpression(token)) or (not isUnsignedInt(token)):
|
|
continue
|
|
if not token.values:
|
|
continue
|
|
for value in token.values:
|
|
if value.intvalue < 0 or value.intvalue > max_uint:
|
|
reportError(token, 12, 4)
|
|
break
|
|
|
|
|
|
def misra_13_1(data):
|
|
for token in data.tokenlist:
|
|
if not simpleMatch(token, '= {'):
|
|
continue
|
|
init = token.next
|
|
if hasSideEffectsRecursive(init):
|
|
reportError(init, 13, 1)
|
|
|
|
|
|
def misra_13_3(data):
|
|
for token in data.tokenlist:
|
|
if token.str not in {'++', '--'}:
|
|
continue
|
|
astTop = token
|
|
while astTop.astParent and astTop.astParent.str not in {',', ';'}:
|
|
astTop = astTop.astParent
|
|
if countSideEffects(astTop) >= 2:
|
|
reportError(astTop, 13, 3)
|
|
|
|
|
|
def misra_13_4(data):
|
|
for token in data.tokenlist:
|
|
if token.str != '=':
|
|
continue
|
|
if not token.astParent:
|
|
continue
|
|
if token.astOperand1.str == '[' and token.astOperand1.previous.str in {'{', ','}:
|
|
continue
|
|
if not (token.astParent.str in [',', ';']):
|
|
reportError(token, 13, 4)
|
|
|
|
|
|
def misra_13_5(data):
|
|
for token in data.tokenlist:
|
|
if token.isLogicalOp and hasSideEffectsRecursive(token.astOperand2):
|
|
reportError(token, 13, 5)
|
|
|
|
|
|
def misra_13_6(data):
|
|
for token in data.tokenlist:
|
|
if token.str == 'sizeof' and hasSideEffectsRecursive(token.next):
|
|
reportError(token, 13, 6)
|
|
|
|
|
|
def misra_14_1(data):
|
|
for token in data.tokenlist:
|
|
if token.str == 'for':
|
|
exprs = getForLoopExpressions(token)
|
|
if not exprs:
|
|
continue
|
|
for counter in findCounterTokens(exprs[1]):
|
|
if counter.valueType and counter.valueType.isFloat():
|
|
reportError(token, 14, 1)
|
|
elif token.str == 'while':
|
|
if isFloatCounterInWhileLoop(token):
|
|
reportError(token, 14, 1)
|
|
|
|
|
|
def misra_14_2(data):
|
|
for token in data.tokenlist:
|
|
expressions = getForLoopExpressions(token)
|
|
if not expressions:
|
|
continue
|
|
if expressions[0] and not expressions[0].isAssignmentOp:
|
|
reportError(token, 14, 2)
|
|
elif hasSideEffectsRecursive(expressions[1]):
|
|
reportError(token, 14, 2)
|
|
|
|
|
|
def misra_14_4(data):
|
|
for token in data.tokenlist:
|
|
if token.str != '(':
|
|
continue
|
|
if not token.astOperand1 or not (token.astOperand1.str in ['if', 'while']):
|
|
continue
|
|
if not isBoolExpression(token.astOperand2):
|
|
reportError(token, 14, 4)
|
|
|
|
|
|
def misra_15_1(data):
|
|
for token in data.tokenlist:
|
|
if token.str == "goto":
|
|
reportError(token, 15, 1)
|
|
|
|
|
|
def misra_15_2(data):
|
|
for token in data.tokenlist:
|
|
if token.str != 'goto':
|
|
continue
|
|
if (not token.next) or (not token.next.isName):
|
|
continue
|
|
if not findGotoLabel(token):
|
|
reportError(token, 15, 2)
|
|
|
|
|
|
def misra_15_3(data):
|
|
for token in data.tokenlist:
|
|
if token.str != 'goto':
|
|
continue
|
|
if (not token.next) or (not token.next.isName):
|
|
continue
|
|
tok = findGotoLabel(token)
|
|
if not tok:
|
|
continue
|
|
scope = token.scope
|
|
while scope and scope != tok.scope:
|
|
scope = scope.nestedIn
|
|
if not scope:
|
|
reportError(token, 15, 3)
|
|
|
|
|
|
def misra_15_5(data):
|
|
for token in data.tokenlist:
|
|
if token.str == 'return' and token.scope.type != 'Function':
|
|
reportError(token, 15, 5)
|
|
|
|
|
|
def misra_15_6(rawTokens):
|
|
state = 0
|
|
indent = 0
|
|
tok1 = None
|
|
for token in rawTokens:
|
|
if token.str in ['if', 'for', 'while']:
|
|
if simpleMatch(token.previous, '# if'):
|
|
continue
|
|
if simpleMatch(token.previous, "} while"):
|
|
# is there a 'do { .. } while'?
|
|
start = rawlink(token.previous)
|
|
if start and simpleMatch(start.previous, 'do {'):
|
|
continue
|
|
if state == 2:
|
|
reportError(tok1, 15, 6)
|
|
state = 1
|
|
indent = 0
|
|
tok1 = token
|
|
elif token.str == 'else':
|
|
if simpleMatch(token.previous, '# else'):
|
|
continue
|
|
if simpleMatch(token, 'else if'):
|
|
continue
|
|
if state == 2:
|
|
reportError(tok1, 15, 6)
|
|
state = 2
|
|
indent = 0
|
|
tok1 = token
|
|
elif state == 1:
|
|
if indent == 0 and token.str != '(':
|
|
state = 0
|
|
continue
|
|
if token.str == '(':
|
|
indent = indent + 1
|
|
elif token.str == ')':
|
|
if indent == 0:
|
|
state = 0
|
|
elif indent == 1:
|
|
state = 2
|
|
indent = indent - 1
|
|
elif state == 2:
|
|
if token.str.startswith('//') or token.str.startswith('/*'):
|
|
continue
|
|
state = 0
|
|
if token.str != '{':
|
|
reportError(tok1, 15, 6)
|
|
|
|
|
|
def misra_15_7(data):
|
|
for token in data.tokenlist:
|
|
if not simpleMatch(token, '}'):
|
|
continue
|
|
if not token.scope.type == 'If':
|
|
continue
|
|
if not token.scope.nestedIn.type == 'Else':
|
|
continue
|
|
if not token.next.str == 'else':
|
|
reportError(token, 15, 7)
|
|
|
|
# TODO add 16.1 rule
|
|
|
|
|
|
def misra_16_2(data):
|
|
for token in data.tokenlist:
|
|
if token.str == 'case' and token.scope.type != 'Switch':
|
|
reportError(token, 16, 2)
|
|
|
|
|
|
def misra_16_3(rawTokens):
|
|
STATE_NONE = 0 # default state, not in switch case/default block
|
|
STATE_BREAK = 1 # break/comment is seen but not its ';'
|
|
STATE_OK = 2 # a case/default is allowed (we have seen 'break;'/'comment'/'{'/attribute)
|
|
state = STATE_NONE
|
|
for token in rawTokens:
|
|
if token.str == 'break' or token.str == 'return' or token.str == 'throw':
|
|
state = STATE_BREAK
|
|
elif token.str == ';':
|
|
if state == STATE_BREAK:
|
|
state = STATE_OK
|
|
else:
|
|
state = STATE_NONE
|
|
elif token.str.startswith('/*') or token.str.startswith('//'):
|
|
if 'fallthrough' in token.str.lower():
|
|
state = STATE_OK
|
|
elif simpleMatch(token, '[ [ fallthrough ] ] ;'):
|
|
state = STATE_BREAK
|
|
elif token.str == '{':
|
|
state = STATE_OK
|
|
elif token.str == '}' and state == STATE_OK:
|
|
# is this {} an unconditional block of code?
|
|
link = findRawLink(token)
|
|
if (link is None) or (link.previous is None) or (link.previous.str not in ':;{}'):
|
|
state = STATE_NONE
|
|
elif token.str == 'case' or token.str == 'default':
|
|
if state != STATE_OK:
|
|
reportError(token, 16, 3)
|
|
state = STATE_OK
|
|
|
|
|
|
def misra_16_4(data):
|
|
for token in data.tokenlist:
|
|
if token.str != 'switch':
|
|
continue
|
|
if not simpleMatch(token, 'switch ('):
|
|
continue
|
|
if not simpleMatch(token.next.link, ') {'):
|
|
continue
|
|
startTok = token.next.link.next
|
|
tok = startTok.next
|
|
while tok and tok.str != '}':
|
|
if tok.str == '{':
|
|
tok = tok.link
|
|
elif tok.str == 'default':
|
|
break
|
|
tok = tok.next
|
|
if tok and tok.str != 'default':
|
|
reportError(token, 16, 4)
|
|
|
|
|
|
def misra_16_5(data):
|
|
for token in data.tokenlist:
|
|
if token.str != 'default':
|
|
continue
|
|
if token.previous and token.previous.str == '{':
|
|
continue
|
|
tok2 = token
|
|
while tok2:
|
|
if tok2.str in {'}', 'case'}:
|
|
break
|
|
if tok2.str == '{':
|
|
tok2 = tok2.link
|
|
tok2 = tok2.next
|
|
if tok2 and tok2.str == 'case':
|
|
reportError(token, 16, 5)
|
|
|
|
|
|
def misra_16_6(data):
|
|
for token in data.tokenlist:
|
|
if not (simpleMatch(token, 'switch (') and simpleMatch(token.next.link, ') {')):
|
|
continue
|
|
tok = token.next.link.next.next
|
|
count = 0
|
|
while tok:
|
|
if tok.str in ['break', 'return', 'throw']:
|
|
count = count + 1
|
|
elif tok.str == '{':
|
|
tok = tok.link
|
|
if isNoReturnScope(tok):
|
|
count = count + 1
|
|
elif tok.str == '}':
|
|
break
|
|
tok = tok.next
|
|
if count < 2:
|
|
reportError(token, 16, 6)
|
|
|
|
|
|
def misra_16_7(data):
|
|
for token in data.tokenlist:
|
|
if simpleMatch(token, 'switch (') and isBoolExpression(token.next.astOperand2):
|
|
reportError(token, 16, 7)
|
|
|
|
|
|
def misra_17_1(data):
|
|
for token in data.tokenlist:
|
|
if isFunctionCall(token) and token.astOperand1.str in {'va_list', 'va_arg', 'va_start', 'va_end', 'va_copy'}:
|
|
reportError(token, 17, 1)
|
|
elif token.str == 'va_list':
|
|
reportError(token, 17, 1)
|
|
|
|
|
|
def misra_17_6(rawTokens):
|
|
for token in rawTokens:
|
|
if simpleMatch(token, '[ static'):
|
|
reportError(token, 17, 6)
|
|
|
|
|
|
def misra_17_8(data):
|
|
for token in data.tokenlist:
|
|
if not (token.isAssignmentOp or (token.str in {'++', '--'})):
|
|
continue
|
|
if not token.astOperand1:
|
|
continue
|
|
var = token.astOperand1.variable
|
|
if var and var.isArgument:
|
|
reportError(token, 17, 8)
|
|
|
|
|
|
def misra_18_5(data):
|
|
for var in data.variables:
|
|
if not var.isPointer:
|
|
continue
|
|
typetok = var.nameToken
|
|
count = 0
|
|
while typetok:
|
|
if typetok.str == '*':
|
|
count = count + 1
|
|
elif not typetok.isName:
|
|
break
|
|
typetok = typetok.previous
|
|
if count > 2:
|
|
reportError(var.nameToken, 18, 5)
|
|
|
|
|
|
def misra_18_8(data):
|
|
for var in data.variables:
|
|
if not var.isArray or not var.isLocal:
|
|
continue
|
|
# TODO Array dimensions are not available in dump, must look in tokens
|
|
typetok = var.nameToken.next
|
|
if not typetok or typetok.str != '[':
|
|
continue
|
|
if not isConstantExpression(typetok.astOperand2):
|
|
reportError(var.nameToken, 18, 8)
|
|
|
|
|
|
def misra_19_2(data):
|
|
for token in data.tokenlist:
|
|
if token.str == 'union':
|
|
reportError(token, 19, 2)
|
|
|
|
|
|
def misra_20_1(data):
|
|
for directive in data.directives:
|
|
if not directive.str.startswith('#include'):
|
|
continue
|
|
for token in data.tokenlist:
|
|
if token.file != directive.file:
|
|
continue
|
|
if int(token.linenr) < int(directive.linenr):
|
|
reportError(directive, 20, 1)
|
|
break
|
|
|
|
|
|
def misra_20_2(data):
|
|
for directive in data.directives:
|
|
if not directive.str.startswith('#include '):
|
|
continue
|
|
for pattern in {'\\', '//', '/*', "'"}:
|
|
if pattern in directive.str:
|
|
reportError(directive, 20, 2)
|
|
break
|
|
|
|
|
|
def misra_20_3(rawTokens):
|
|
linenr = -1
|
|
for token in rawTokens:
|
|
if token.str.startswith('/') or token.linenr == linenr:
|
|
continue
|
|
linenr = token.linenr
|
|
if not simpleMatch(token, '# include'):
|
|
continue
|
|
headerToken = token.next.next
|
|
if not headerToken or not (headerToken.str.startswith('<') or headerToken.str.startswith('"')):
|
|
reportError(token, 20, 3)
|
|
|
|
|
|
def misra_20_4(data):
|
|
for directive in data.directives:
|
|
res = re.search(r'#define ([a-z][a-z0-9_]+)', directive.str)
|
|
if res and (res.group(1) in KEYWORDS):
|
|
reportError(directive, 20, 4)
|
|
|
|
|
|
def misra_20_5(data):
|
|
for directive in data.directives:
|
|
if directive.str.startswith('#undef '):
|
|
reportError(directive, 20, 5)
|
|
|
|
|
|
def misra_20_13(data):
|
|
for directive in data.directives:
|
|
dir = directive.str
|
|
if dir.find(' ') > 0:
|
|
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']:
|
|
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.
|
|
ifStack = []
|
|
for directive in data.directives:
|
|
if directive.str.startswith('#if ') or directive.str.startswith('#ifdef ') or directive.str.startswith('#ifndef '):
|
|
ifStack.append(directive)
|
|
elif directive.str == '#else' or directive.str.startswith('#elif '):
|
|
if len(ifStack) == 0:
|
|
reportError(directive, 20, 14)
|
|
ifStack.append(directive)
|
|
elif directive.file != ifStack[-1].file:
|
|
reportError(directive, 20, 14)
|
|
elif directive.str == '#endif':
|
|
if len(ifStack) == 0:
|
|
reportError(directive, 20, 14)
|
|
elif directive.file != ifStack[-1].file:
|
|
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'}):
|
|
reportError(token, 21, 3)
|
|
|
|
|
|
def misra_21_4(data):
|
|
directive = findInclude(data.directives, '<setjmp.h>')
|
|
if directive:
|
|
reportError(directive, 21, 4)
|
|
|
|
|
|
def misra_21_5(data):
|
|
directive = findInclude(data.directives, '<signal.h>')
|
|
if directive:
|
|
reportError(directive, 21, 5)
|
|
|
|
|
|
def misra_21_6(data):
|
|
dir_stdio = findInclude(data.directives, '<stdio.h>')
|
|
dir_wchar = findInclude(data.directives, '<wchar.h>')
|
|
if dir_stdio:
|
|
reportError(dir_stdio, 21, 6)
|
|
if dir_wchar:
|
|
reportError(dir_wchar, 21, 6)
|
|
|
|
|
|
def misra_21_7(data):
|
|
for token in data.tokenlist:
|
|
if isFunctionCall(token) and (token.astOperand1.str in {'atof', 'atoi', 'atol', 'atoll'}):
|
|
reportError(token, 21, 7)
|
|
|
|
|
|
def misra_21_8(data):
|
|
for token in data.tokenlist:
|
|
if isFunctionCall(token) and (token.astOperand1.str in {'abort', 'exit', 'getenv', 'system'}):
|
|
reportError(token, 21, 8)
|
|
|
|
|
|
def misra_21_9(data):
|
|
for token in data.tokenlist:
|
|
if (token.str in {'bsearch', 'qsort'}) and token.next and token.next.str == '(':
|
|
reportError(token, 21, 9)
|
|
|
|
|
|
def misra_21_10(data):
|
|
directive = findInclude(data.directives, '<time.h>')
|
|
if directive:
|
|
reportError(directive, 21, 10)
|
|
|
|
for token in data.tokenlist:
|
|
if (token.str == 'wcsftime') and token.next and token.next.str == '(':
|
|
reportError(token, 21, 10)
|
|
|
|
|
|
def misra_21_11(data):
|
|
directive = findInclude(data.directives, '<tgmath.h>')
|
|
if directive:
|
|
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
|
|
appendixA = False
|
|
ruleText = False
|
|
global ruleTexts
|
|
Rule_pattern = re.compile(r'^Rule ([0-9]+).([0-9]+)')
|
|
Choice_pattern = re.compile(r'^[ ]*(Advisory|Required|Mandatory)$')
|
|
xA_Z_pattern = re.compile(r'^[#A-Z].*')
|
|
a_z_pattern = re.compile(r'^[a-z].*')
|
|
for line in open(filename, 'rt'):
|
|
line = line.replace('\r', '').replace('\n', '')
|
|
if len(line) == 0:
|
|
if ruleText:
|
|
num1 = 0
|
|
num2 = 0
|
|
ruleText = False
|
|
continue
|
|
if not appendixA:
|
|
if line.find('Appendix A') >= 0 and line.find('Summary of guidelines') >= 10:
|
|
appendixA = True
|
|
continue
|
|
if line.find('Appendix B') >= 0:
|
|
break
|
|
res = Rule_pattern.match(line)
|
|
if res:
|
|
num1 = int(res.group(1))
|
|
num2 = int(res.group(2))
|
|
ruleText = False
|
|
continue
|
|
if Choice_pattern.match(line):
|
|
ruleText = False
|
|
elif xA_Z_pattern.match(line):
|
|
if ruleText:
|
|
num2 = num2 + 1
|
|
num = num1 * 100 + num2
|
|
ruleTexts[num] = line
|
|
ruleText = True
|
|
elif ruleText and a_z_pattern.match(line):
|
|
num = num1 * 100 + num2
|
|
ruleTexts[num] = ruleTexts[num] + ' ' + line
|
|
continue
|
|
|
|
|
|
def generateTable():
|
|
numberOfRules = {}
|
|
numberOfRules[1] = 3
|
|
numberOfRules[2] = 7
|
|
numberOfRules[3] = 2
|
|
numberOfRules[4] = 2
|
|
numberOfRules[5] = 9
|
|
numberOfRules[6] = 2
|
|
numberOfRules[7] = 4
|
|
numberOfRules[8] = 14
|
|
numberOfRules[9] = 5
|
|
numberOfRules[10] = 8
|
|
numberOfRules[11] = 9
|
|
numberOfRules[12] = 4
|
|
numberOfRules[13] = 6
|
|
numberOfRules[14] = 4
|
|
numberOfRules[15] = 7
|
|
numberOfRules[16] = 7
|
|
numberOfRules[17] = 8
|
|
numberOfRules[18] = 8
|
|
numberOfRules[19] = 2
|
|
numberOfRules[20] = 14
|
|
numberOfRules[21] = 12
|
|
numberOfRules[22] = 6
|
|
|
|
# what rules are handled by this addon?
|
|
addon = []
|
|
compiled = re.compile(r'[ ]+misra_([0-9]+)_([0-9]+)[(].*')
|
|
for line in open('__file__'):
|
|
res = compiled.match(line)
|
|
if res is None:
|
|
continue
|
|
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']
|
|
|
|
# rules that can be checked with compilers
|
|
# compiler = ['1.1', '1.2']
|
|
|
|
# print table
|
|
for i1 in range(1, 23):
|
|
for i2 in range(1, numberOfRules[i1] + 1):
|
|
num = str(i1) + '.' + str(i2)
|
|
s = ''
|
|
if num in addon:
|
|
s = 'X (Addon)'
|
|
elif num in cppcheck:
|
|
s = 'X (Cppcheck)'
|
|
num = num + ' '
|
|
print(num[:8] + s)
|
|
sys.exit(1)
|
|
|
|
|
|
def parseDump(dumpfile):
|
|
|
|
data = cppcheckdata.parsedump(dumpfile)
|
|
|
|
global suppressions
|
|
suppressions = data.suppressions
|
|
|
|
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:
|
|
for tok in data.rawTokens:
|
|
if tok.str.startswith('//') and 'TODO' not in tok.str:
|
|
compiled = re.compile(r'[0-9]+\.[0-9]+')
|
|
for word in tok.str[2:].split(' '):
|
|
if compiled.match(word):
|
|
VERIFY_EXPECTED.append(str(tok.linenr) + ':' + word)
|
|
else:
|
|
printStatus('Checking ' + dumpfile + '...')
|
|
|
|
cfgNumber = 0
|
|
|
|
for cfg in data.configurations:
|
|
cfgNumber = cfgNumber + 1
|
|
if len(data.configurations) > 1:
|
|
printStatus('Checking ' + dumpfile + ', config "' + cfg.name + '"...')
|
|
|
|
if cfgNumber == 1:
|
|
misra_3_1(data.rawTokens)
|
|
misra_4_1(data.rawTokens)
|
|
misra_5_1(cfg)
|
|
misra_5_2(cfg)
|
|
misra_5_3(cfg)
|
|
misra_5_4(cfg)
|
|
misra_5_5(cfg)
|
|
# 6.1 require updates in Cppcheck (type info for bitfields are lost)
|
|
# 6.2 require updates in Cppcheck (type info for bitfields are lost)
|
|
if cfgNumber == 1:
|
|
misra_7_1(data.rawTokens)
|
|
misra_7_3(data.rawTokens)
|
|
misra_8_11(cfg)
|
|
misra_8_12(cfg)
|
|
if cfgNumber == 1:
|
|
misra_8_14(data.rawTokens)
|
|
misra_9_5(data.rawTokens)
|
|
misra_10_1(cfg)
|
|
misra_10_4(cfg)
|
|
misra_10_6(cfg)
|
|
misra_10_8(cfg)
|
|
misra_11_3(cfg)
|
|
misra_11_4(cfg)
|
|
misra_11_5(cfg)
|
|
misra_11_6(cfg)
|
|
misra_11_7(cfg)
|
|
misra_11_8(cfg)
|
|
misra_11_9(cfg)
|
|
if cfgNumber == 1:
|
|
misra_12_1_sizeof(data.rawTokens)
|
|
misra_12_1(cfg)
|
|
misra_12_2(cfg)
|
|
misra_12_3(cfg)
|
|
misra_12_4(cfg)
|
|
misra_13_1(cfg)
|
|
misra_13_3(cfg)
|
|
misra_13_4(cfg)
|
|
misra_13_5(cfg)
|
|
misra_13_6(cfg)
|
|
misra_14_1(cfg)
|
|
misra_14_2(cfg)
|
|
misra_14_4(cfg)
|
|
misra_15_1(cfg)
|
|
misra_15_2(cfg)
|
|
misra_15_3(cfg)
|
|
misra_15_5(cfg)
|
|
if cfgNumber == 1:
|
|
misra_15_6(data.rawTokens)
|
|
misra_15_7(cfg)
|
|
misra_16_2(cfg)
|
|
if cfgNumber == 1:
|
|
misra_16_3(data.rawTokens)
|
|
misra_16_4(cfg)
|
|
misra_16_5(cfg)
|
|
misra_16_6(cfg)
|
|
misra_16_7(cfg)
|
|
misra_17_1(cfg)
|
|
if cfgNumber == 1:
|
|
misra_17_6(data.rawTokens)
|
|
misra_17_8(cfg)
|
|
misra_18_5(cfg)
|
|
misra_18_8(cfg)
|
|
misra_19_2(cfg)
|
|
misra_20_1(cfg)
|
|
misra_20_2(cfg)
|
|
if cfgNumber == 1:
|
|
misra_20_3(data.rawTokens)
|
|
misra_20_4(cfg)
|
|
misra_20_5(cfg)
|
|
misra_20_13(cfg)
|
|
misra_20_14(cfg)
|
|
misra_21_3(cfg)
|
|
misra_21_4(cfg)
|
|
misra_21_5(cfg)
|
|
misra_21_6(cfg)
|
|
misra_21_7(cfg)
|
|
misra_21_8(cfg)
|
|
misra_21_9(cfg)
|
|
misra_21_10(cfg)
|
|
misra_21_11(cfg)
|
|
# 22.4 is already covered by Cppcheck writeReadOnlyFile
|
|
|
|
exitCode = 0
|
|
if VERIFY:
|
|
for expected in VERIFY_EXPECTED:
|
|
if expected not in VERIFY_ACTUAL:
|
|
print('Expected but not seen: ' + expected)
|
|
exitCode = 1
|
|
for actual in VERIFY_ACTUAL:
|
|
if actual not in VERIFY_EXPECTED:
|
|
print('Not expected: ' + actual)
|
|
exitCode = 1
|
|
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)
|