misra.py: Fixup rules severity (#1911)

* misra.py: Fixup rules severity

* Divide cppcheck and MISRA severity.
This commit is contained in:
Georgy Komarov 2019-06-23 15:08:05 +03:00 committed by Daniel Marjamäki
parent bd59999fd0
commit 05bb4a0c8f
3 changed files with 91 additions and 21 deletions

View File

@ -566,26 +566,39 @@ def remove_file_prefix(file_path, prefix):
result = file_path result = file_path
return result return result
class Rule:
class Rule(object):
"""Class to keep rule text and metadata""" """Class to keep rule text and metadata"""
MISRA_SEVERIY_LEVELS = ['Required', 'Mandatory', 'Advisory']
def __init__(self, num1, num2): def __init__(self, num1, num2):
self.num1 = num1 self.num1 = num1
self.num2 = num2 self.num2 = num2
self.text = '' self.text = ''
self.severity = 'style' self.misra_severity = ''
@property @property
def num(self): def num(self):
return self.num1 * 100 + self.num2 return self.num1 * 100 + self.num2
@property
def misra_severity(self):
return self._misra_severity
@misra_severity.setter
def misra_severity(self, val):
if val in self.MISRA_SEVERIY_LEVELS:
self._misra_severity = val
else:
self._misra_severity = ''
@property @property
def cppcheck_severity(self): def cppcheck_severity(self):
return self.SEVERITY_MAP[self.severity] return 'style'
def __repr__(self): def __repr__(self):
return "%d.%d %s" % (self.num1, self.num2, self.severity) return "%d.%d (%s)" % (self.num1, self.num2, self.misra_severity)
SEVERITY_MAP = { 'Required': 'warning', 'Mandatory': 'error', 'Advisory': 'style', 'style': 'style' }
class MisraSettings(object): class MisraSettings(object):
@ -2064,19 +2077,23 @@ class MisraChecker:
return return
else: else:
errorId = 'c2012-' + str(num1) + '.' + str(num2) errorId = 'c2012-' + str(num1) + '.' + str(num2)
severity = 'style' misra_severity = 'Undefined'
cppcheck_severity = 'style'
if ruleNum in self.ruleTexts: if ruleNum in self.ruleTexts:
errmsg = self.ruleTexts[ruleNum].text errmsg = self.ruleTexts[ruleNum].text
severity = self.ruleTexts[ruleNum].cppcheck_severity if self.ruleTexts[ruleNum].misra_severity:
misra_severity = self.ruleTexts[ruleNum].misra_severity
errmsg += ''.join((' (', self.ruleTexts[ruleNum].misra_severity, ')'))
cppcheck_severity = self.ruleTexts[ruleNum].cppcheck_severity
elif len(self.ruleTexts) == 0: elif len(self.ruleTexts) == 0:
errmsg = 'misra violation (use --rule-texts=<file> to get proper output)' errmsg = 'misra violation (use --rule-texts=<file> to get proper output)'
else: else:
return return
cppcheckdata.reportError(location, severity, errmsg, 'misra', errorId) cppcheckdata.reportError(location, cppcheck_severity, errmsg, 'misra', errorId)
if not severity in self.violations: if not misra_severity in self.violations:
self.violations[severity] = [] self.violations[misra_severity] = []
self.violations[severity].append('misra-' + errorId) self.violations[misra_severity].append('misra-' + errorId)
def loadRuleTexts(self, filename): def loadRuleTexts(self, filename):
num1 = 0 num1 = 0
@ -2086,7 +2103,7 @@ class MisraChecker:
expect_more = False expect_more = False
Rule_pattern = re.compile(r'^Rule ([0-9]+).([0-9]+)') Rule_pattern = re.compile(r'^Rule ([0-9]+).([0-9]+)')
Choice_pattern = re.compile(r'^[ ]*(Advisory|Required|Mandatory)$') Choice_pattern = re.compile(r'.*[ ]*(Advisory|Required|Mandatory)$')
xA_Z_pattern = re.compile(r'^[#A-Z].*') xA_Z_pattern = re.compile(r'^[#A-Z].*')
a_z_pattern = re.compile(r'^[a-z].*') a_z_pattern = re.compile(r'^[a-z].*')
# Try to detect the file encoding # Try to detect the file encoding
@ -2133,7 +2150,7 @@ class MisraChecker:
rule = Rule(num1, num2) rule = Rule(num1, num2)
res = Choice_pattern.match(line) res = Choice_pattern.match(line)
if res: if res:
self.ruleTexts[rule.num].severity = res.group(1) rule.misra_severity = res.group(1)
expect_more = False expect_more = False
continue continue
if rule is None: if rule is None:
@ -2408,12 +2425,13 @@ def main():
exitCode = 1 exitCode = 1
if settings.show_summary: if settings.show_summary:
print("\nMISRA rule violations found: %s\n" % ("\t".join([ "%s: %d" % (viol, len(checker.get_violations(viol))) for viol in checker.get_violation_types()]))) print("\nMISRA rules violations found:\n\t%s\n" % ("\n\t".join([ "%s: %d" % (viol, len(checker.get_violations(viol))) for viol in checker.get_violation_types()])))
rules_violated = {} rules_violated = {}
for severity, ids in checker.get_violations(): for severity, ids in checker.get_violations():
for misra_id in ids: for misra_id in ids:
rules_violated[misra_id] = rules_violated.get(misra_id, 0) + 1 rules_violated[misra_id] = rules_violated.get(misra_id, 0) + 1
print("Misra rules violated:") print("MISRA rules violated:")
convert = lambda text: int(text) if text.isdigit() else text convert = lambda text: int(text) if text.isdigit() else text
misra_sort = lambda key: [ convert(c) for c in re.split('[\.-]([0-9]*)', key) ] misra_sort = lambda key: [ convert(c) for c in re.split('[\.-]([0-9]*)', key) ]
for misra_id in sorted(rules_violated.keys(), key=misra_sort): for misra_id in sorted(rules_violated.keys(), key=misra_sort):
@ -2424,7 +2442,7 @@ def main():
num = int(res.group(1)) * 100 + int(res.group(2)) num = int(res.group(1)) * 100 + int(res.group(2))
severity = '-' severity = '-'
if num in checker.ruleTexts: if num in checker.ruleTexts:
severity = checker.ruleTexts[num].severity severity = checker.ruleTexts[num].cppcheck_severity
print("\t%15s (%s): %d" % (misra_id, severity, rules_violated[misra_id])) print("\t%15s (%s): %d" % (misra_id, severity, rules_violated[misra_id]))
if args.show_suppressed_rules: if args.show_suppressed_rules:

View File

@ -3,7 +3,7 @@ Rule 3.1 Required
R3.1 text. R3.1 text.
Rule 4.1 Required Rule 4.1 Required
R4.1 text. R4.1 text.
Rule 10.4 Required Rule 10.4 Mandatory
R10.4 text. R10.4 text.
Rule 11.5 Advisory Rule 11.5 Advisory
R11.5 text. R11.5 text.
@ -17,4 +17,6 @@ Rule 20.1 Advisory
R20.1 text. R20.1 text.
Rule 21.3 Required Rule 21.3 Required
R21.3 text. R21.3 text.
Rule 21.4
R21.4 text.

View File

@ -5,9 +5,10 @@ try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from io import StringIO from io import StringIO
import subprocess
class Capturing(object): class CapturingStdout(object):
def __enter__(self): def __enter__(self):
self._stdout = sys.stdout self._stdout = sys.stdout
@ -21,6 +22,37 @@ class Capturing(object):
sys.stdout = self._stdout sys.stdout = self._stdout
class CapturingStderr(object):
def __enter__(self):
self._stderr = sys.stderr
sys.stderr = self._stringio = StringIO()
self.captured = []
return self
def __exit__(self, *args):
self.captured.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stderr = self._stderr
TEST_SOURCE_FILES = ['./addons/test/misra-test.c']
def setup_module(module):
for f in TEST_SOURCE_FILES:
p = subprocess.Popen(["./cppcheck", "--dump", "--quiet", f])
p.communicate()[0]
if p.returncode != 0:
raise OSError("cppcheck returns error code: %d" % p.returncode)
subprocess.Popen(["sync"])
def teardown_module(module):
for f in TEST_SOURCE_FILES:
subprocess.Popen(["rm", "-f", f + ".dump"])
@pytest.fixture @pytest.fixture
def checker(): def checker():
from addons.misra import MisraChecker, MisraSettings, get_args from addons.misra import MisraChecker, MisraSettings, get_args
@ -54,8 +86,26 @@ def test_loadRuleTexts_mutiple_lines(checker):
def test_verifyRuleTexts(checker): def test_verifyRuleTexts(checker):
checker.loadRuleTexts("./addons/test/assets/misra_rules_dummy.txt") checker.loadRuleTexts("./addons/test/assets/misra_rules_dummy.txt")
with Capturing() as output: with CapturingStdout() as output:
checker.verifyRuleTexts() checker.verifyRuleTexts()
captured = ''.join(output.captured) captured = ''.join(output.captured)
assert("21.3" not in captured) assert("21.3" not in captured)
assert("1.3" in captured) assert("1.3" in captured)
def test_rules_misra_severity(checker):
checker.loadRuleTexts("./addons/test/assets/misra_rules_dummy.txt")
assert(checker.ruleTexts[1004].misra_severity == 'Mandatory')
assert(checker.ruleTexts[401].misra_severity == 'Required')
assert(checker.ruleTexts[1505].misra_severity == 'Advisory')
assert(checker.ruleTexts[2104].misra_severity == '')
def test_rules_cppcheck_severity(checker):
checker.loadRuleTexts("./addons/test/assets/misra_rules_dummy.txt")
with CapturingStderr() as output:
checker.parseDump("./addons/test/misra-test.c.dump")
captured = ''.join(output.captured)
assert("(error)" not in captured)
assert("(warning)" not in captured)
assert("(style)" in captured)