Fix per file excludes (#1437)

* MISRA: Allow printing of the suppressed rules to the console

--show-suppressed-rules will print rules in the suppression rule list to
the console sorted by rule number.

* MISRA: Correct rule suppression for entire file scope

The entire file scope suppression check was checking for the rule item
list to be None instead of looking for None as an entry into the list.

Correct this check and modify the documentation to explicitly state that
an entry of None in the rule item list will set the scope for that
suppression to be the entire file.

* MISRA: Tests for checking per-file rule suppressions

To run:

../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c
python ../misra.py misra-suppressions*-test.c.dump

There should be no violations reported

* MISRA: Allow ignoring a prefix from file paths when suppression matching

For environments that run cppcheck from the build system cppcheck may be
passed a filename that is a complete path.

Often this path will include a portion that is specific to the developer
or to the environment where the project is located.

The per-file suppression rules do filename matching based on the
filename passed to cppcheck. To match any path information also has to
be included into the suppressions file provided to cppcheck via the
--suppressions-list= option.

This limits the usefulness of the per-file based suppressions because
it requires the suppression to be customized on a per instance basis.

Add a option "--file-prefix" that allows a prefix to be excluded from
the file path when doing the suppression filename matching.

Example.

Given the following structure:

/test/path1/misra-suppressions1-test.c
/test/path1/misra-suppressions2-test.c

specifying --file-prefix /test/path1 will allow the use of
misra-suppressions1-test.c and misra-suppressions2-test.c as filenames
in the suppressions file without leading patch information but still
match the suppression rule.

* MISRA: Tests for --file-prefix option

To run:

../../cppcheck --suppressions-list=suppressions.txt \
    --dump misra-suppressions*-test.c \
           path1/misra-suppressions*-test.c

python ../misra.py misra-suppressions*-test.c.dump \
                   path1/misra-suppressions*-test.c

There should be no violations reported
This commit is contained in:
Richard A. Smith 2018-10-18 03:17:57 -04:00 committed by Daniel Marjamäki
parent 4dbdc934b8
commit f286325cec
6 changed files with 197 additions and 19 deletions

View File

@ -521,6 +521,30 @@ def generateTable():
sys.exit(1) sys.exit(1)
def remove_file_prefix(file_path, prefix):
"""
Remove a file path prefix from a give path. leftover
directory seperators at the beginning of a file
after the removal are also stripped.
Example:
'/remove/this/path/file.c'
with a prefix of:
'/remove/this/path'
becomes:
file.c
"""
result = None
if file_path.startswith(prefix):
result = file_path[len(prefix):]
# Remove any leftover directory seperators at the
# beginning
result = result.lstrip('\\/')
else:
result = file_path
return result
class MisraChecker: class MisraChecker:
def __init__(self): def __init__(self):
@ -543,7 +567,7 @@ class MisraChecker:
# Major * 100 + minor. ie Rule 5.2 = (5*100) + 2 # Major * 100 + minor. ie Rule 5.2 = (5*100) + 2
# Dict 2 is keyed by filename. An entry of None means suppress globaly. # Dict 2 is keyed by filename. An entry of None means suppress globaly.
# Each file name entry contails a list of tuples of (lineNumber, symbolName) # Each file name entry contails a list of tuples of (lineNumber, symbolName)
# or None which indicates suppress rule for the entire file. # or an item of None which indicates suppress rule for the entire file.
# The line and symbol name tuple may have None as either of its elements but # The line and symbol name tuple may have None as either of its elements but
# should not be None for both. # should not be None for both.
self.suppressedRules = dict() self.suppressedRules = dict()
@ -551,6 +575,8 @@ class MisraChecker:
# List of suppression extracted from the dumpfile # List of suppression extracted from the dumpfile
self.dumpfileSuppressions = None self.dumpfileSuppressions = None
# Prefix to ignore when matching suppression files.
self.filePrefix = None
def misra_3_1(self, rawTokens): def misra_3_1(self, rawTokens):
for token in rawTokens: for token in rawTokens:
@ -1678,13 +1704,17 @@ class MisraChecker:
format. The value of that dictionary is a dictionary of filenames. format. The value of that dictionary is a dictionary of filenames.
If the value is None then the rule is assumed to be suppressed for If the value is None then the rule is assumed to be suppressed for
all files. all files.
If the filename exists then the value of that dictionary contains the If the filename exists then the value of that dictionary contains a list
scope of the suppression. If the value is None then the rule is assumed with the scope of the suppression. If the list contains an item of None
to be suppresed for the entire file. Otherwise the value of the dictionary then the rule is assumed to be suppresed for the entire file. Otherwise
is a list of line number, symbol name tuples. the list contains line number, symbol name tuples.
For each tuple either line number or symbol name can can be none. For each tuple either line number or symbol name can can be none.
""" """
normalized_filename = None
if fileName is not None:
normalized_filename = os.path.normpath(fileName)
if lineNumber is not None or symbolName is not None: if lineNumber is not None or symbolName is not None:
line_symbol = (lineNumber, symbolName) line_symbol = (lineNumber, symbolName)
@ -1697,7 +1727,7 @@ class MisraChecker:
ruleItemList.append(line_symbol) ruleItemList.append(line_symbol)
fileDict = dict() fileDict = dict()
fileDict[fileName] = ruleItemList fileDict[normalized_filename] = ruleItemList
self.suppressedRules[ruleNum] = fileDict self.suppressedRules[ruleNum] = fileDict
@ -1711,22 +1741,21 @@ class MisraChecker:
fileDict = self.suppressedRules[ruleNum] fileDict = self.suppressedRules[ruleNum]
# If the filename is not in the dict already add it # If the filename is not in the dict already add it
if not fileName in fileDict: if not normalized_filename in fileDict:
ruleItemList = list() ruleItemList = list()
ruleItemList.append(line_symbol) ruleItemList.append(line_symbol)
fileDict[fileName] = ruleItemList fileDict[normalized_filename] = ruleItemList
# Rule is added with a file scope. Done # Rule is added with a file scope. Done
return return
# Rule has a matching filename. Check for # Rule has a matching filename. Get the rule item list.
# rule a rule item list.
# If it exists then check the lists of rule items # Check the lists of rule items
# to see if the lineNumber, symbonName combination # to see if this (lineNumber, symbonName) combination
# exists # or None already exists.
ruleItemList = fileDict[fileName] ruleItemList = fileDict[normalized_filename]
if line_symbol is None: if line_symbol is None:
# is it already in the list? # is it already in the list?
@ -1763,9 +1792,16 @@ class MisraChecker:
""" """
ruleIsSuppressed = False ruleIsSuppressed = False
filename = location.file
linenr = location.linenr linenr = location.linenr
# Remove any prefix listed in command arguments from the filename.
filename = None
if location.file is not None:
if self.filePrefix is not None:
filename = remove_file_prefix(location.file, self.filePrefix)
else:
filename = location.file
if ruleNum in self.suppressedRules: if ruleNum in self.suppressedRules:
fileDict = self.suppressedRules[ruleNum] fileDict = self.suppressedRules[ruleNum]
@ -1780,9 +1816,9 @@ class MisraChecker:
# Get the list of ruleItems # Get the list of ruleItems
ruleItemList = fileDict[filename] ruleItemList = fileDict[filename]
if ruleItemList is None: if None in ruleItemList:
# None for itemRuleList means the rule is suppressed # Entry of None in the ruleItemList means the rule is
# for all lines in the filename # suppressed for all lines in the filename
ruleIsSuppressed = True ruleIsSuppressed = True
else: else:
# Iterate though the the list of line numbers # Iterate though the the list of line numbers
@ -1818,6 +1854,39 @@ class MisraChecker:
each.lineNumber, each.symbolName) each.lineNumber, each.symbolName)
def showSuppressedRules(self):
"""
Print out rules in suppression list sorted by Rule Number
"""
print("Suppressed Rules List:")
outlist = list()
for ruleNum in self.suppressedRules:
fileDict = self.suppressedRules[ruleNum]
for fname in fileDict:
ruleItemList = fileDict[fname]
for item in ruleItemList:
if item is None:
item_str = "None"
else:
item_str = str(item[0])
outlist.append("%s: %s: %s" % (float(ruleNum)/100,fname,item_str))
for line in sorted(outlist, reverse=True):
print(" %s" % line)
def setFilePrefix(self, prefix):
"""
Set the file prefix to ignnore from files when matching
supression files
"""
self.filePrefix = prefix
def setSuppressionList(self, suppressionlist): def setSuppressionList(self, suppressionlist):
num1 = 0 num1 = 0
num2 = 0 num2 = 0
@ -2064,6 +2133,8 @@ parser.add_argument("--no-summary", help="Hide summary of violations", action="s
parser.add_argument("-verify", help=argparse.SUPPRESS, 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("-generate-table", help=argparse.SUPPRESS, action="store_true")
parser.add_argument("dumpfile", nargs='*', help="Path of dump file from cppcheck") parser.add_argument("dumpfile", nargs='*', help="Path of dump file from cppcheck")
parser.add_argument("--show-suppressed-rules", help="Print rule suppression list", action="store_true")
parser.add_argument("-P", "--file-prefix", type=str, help="Prefix to strip when matching suppression file rules")
args = parser.parse_args() args = parser.parse_args()
checker = MisraChecker() checker = MisraChecker()
@ -2083,6 +2154,9 @@ else:
if args.suppress_rules: if args.suppress_rules:
checker.setSuppressionList(args.suppress_rules) checker.setSuppressionList(args.suppress_rules)
if args.file_prefix:
checker.setFilePrefix(args.file_prefix)
if args.quiet: if args.quiet:
QUIET = True QUIET = True
if args.no_summary: if args.no_summary:
@ -2122,4 +2196,7 @@ else:
if SHOW_SUMMARY: if SHOW_SUMMARY:
print("\nMISRA rule violations found: %d\n" % (number_of_violations)) print("\nMISRA rule violations found: %d\n" % (number_of_violations))
if args.show_suppressed_rules:
checker.showSuppressedRules()
sys.exit(exitCode) sys.exit(exitCode)

View File

@ -0,0 +1,33 @@
// To test:
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
// There should be no violations reported
// This needs to stay at line number 7 to make the test pass
// If it is changed update suppressions.txt with the new line number
#include <stdio.h> //21.6
extern int misra_5_2_var_hides_var______31x;
static int misra_5_2_var_hides_var______31y;//5.2
static int misra_5_2_function_hides_var_31x;
void misra_5_2_function_hides_var_31y(void) {}//5.2
void foo(void)
{
int i;
switch(misra_5_2_func1()) //16.4 16.6
{
case 1:
{
do
{
for(i = 0; i < 10; i++)
{
if(misra_5_2_func3()) //14.4
{
int misra_5_2_var_hides_var_1____31x;
int misra_5_2_var_hides_var_1____31y;//5.2
}
}
} while(misra_5_2_func2()); //14.4
}
}
}

View File

@ -0,0 +1,14 @@
// To test:
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
// There should be no violations reported
union misra_5_2_field_hides_field__63x { //19.2
int misra_5_2_field_hides_field__31x;
int misra_5_2_field_hides_field__31y;//5.2
};
struct misra_5_2_field_hides_field__63y { //5.2
int misra_5_2_field_hides_field1_31x;
int misra_5_2_field_hides_field1_31y;//5.2
};
const char *s41_1 = "\x41g"; // 4.1
const char *s41_2 = "\x41\x42";

View File

@ -0,0 +1,33 @@
// To test:
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
// There should be no violations reported
// This needs to stay at line number 7 to make the test pass
// If it is changed update suppressions.txt with the new line number
#include <stdio.h> //21.6
extern int misra_5_2_var_hides_var______31x;
static int misra_5_2_var_hides_var______31y;//5.2
static int misra_5_2_function_hides_var_31x;
void misra_5_2_function_hides_var_31y(void) {}//5.2
void foo(void)
{
int i;
switch(misra_5_2_func1()) //16.4 16.6
{
case 1:
{
do
{
for(i = 0; i < 10; i++)
{
if(misra_5_2_func3()) //14.4
{
int misra_5_2_var_hides_var_1____31x;
int misra_5_2_var_hides_var_1____31y;//5.2
}
}
} while(misra_5_2_func2()); //14.4
}
}
}

View File

@ -0,0 +1,14 @@
// To test:
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
// There should be no violations reported
union misra_5_2_field_hides_field__63x { //19.2
int misra_5_2_field_hides_field__31x;
int misra_5_2_field_hides_field__31y;//5.2
};
struct misra_5_2_field_hides_field__63y { //5.2
int misra_5_2_field_hides_field1_31x;
int misra_5_2_field_hides_field1_31y;//5.2
};
const char *s41_1 = "\x41g"; // 4.1
const char *s41_2 = "\x41\x42";

View File

@ -0,0 +1,7 @@
misra_21.6:misra-suppressions1-test.c:7
misra_14_4
misra.5.2
MISRA_16_4:misra-suppressions1-test.c
MISRA.16.6:misra-suppressions1-test.c
MISRA_4_1:misra-suppressions2-test.c
MISRA.19_2:misra-suppressions2-test.c