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)
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:
def __init__(self):
@ -543,7 +567,7 @@ class MisraChecker:
# Major * 100 + minor. ie Rule 5.2 = (5*100) + 2
# 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)
# 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
# should not be None for both.
self.suppressedRules = dict()
@ -551,6 +575,8 @@ class MisraChecker:
# List of suppression extracted from the dumpfile
self.dumpfileSuppressions = None
# Prefix to ignore when matching suppression files.
self.filePrefix = None
def misra_3_1(self, rawTokens):
for token in rawTokens:
@ -1678,13 +1704,17 @@ class MisraChecker:
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
all files.
If the filename exists then the value of that dictionary contains the
scope of the suppression. If the value is None then the rule is assumed
to be suppresed for the entire file. Otherwise the value of the dictionary
is a list of line number, symbol name tuples.
If the filename exists then the value of that dictionary contains a list
with the scope of the suppression. If the list contains an item of None
then the rule is assumed to be suppresed for the entire file. Otherwise
the list contains line number, symbol name tuples.
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:
line_symbol = (lineNumber, symbolName)
@ -1697,7 +1727,7 @@ class MisraChecker:
ruleItemList.append(line_symbol)
fileDict = dict()
fileDict[fileName] = ruleItemList
fileDict[normalized_filename] = ruleItemList
self.suppressedRules[ruleNum] = fileDict
@ -1711,22 +1741,21 @@ class MisraChecker:
fileDict = self.suppressedRules[ruleNum]
# 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.append(line_symbol)
fileDict[fileName] = ruleItemList
fileDict[normalized_filename] = ruleItemList
# Rule is added with a file scope. Done
return
# Rule has a matching filename. Check for
# rule a rule item list.
# Rule has a matching filename. Get the rule item list.
# If it exists then check the lists of rule items
# to see if the lineNumber, symbonName combination
# exists
ruleItemList = fileDict[fileName]
# Check the lists of rule items
# to see if this (lineNumber, symbonName) combination
# or None already exists.
ruleItemList = fileDict[normalized_filename]
if line_symbol is None:
# is it already in the list?
@ -1763,8 +1792,15 @@ class MisraChecker:
"""
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:
fileDict = self.suppressedRules[ruleNum]
@ -1780,9 +1816,9 @@ class MisraChecker:
# Get the list of ruleItems
ruleItemList = fileDict[filename]
if ruleItemList is None:
# None for itemRuleList means the rule is suppressed
# for all lines in the filename
if None in ruleItemList:
# Entry of None in the ruleItemList means the rule is
# suppressed for all lines in the filename
ruleIsSuppressed = True
else:
# Iterate though the the list of line numbers
@ -1818,6 +1854,39 @@ class MisraChecker:
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):
num1 = 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("-generate-table", help=argparse.SUPPRESS, action="store_true")
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()
checker = MisraChecker()
@ -2083,6 +2154,9 @@ else:
if args.suppress_rules:
checker.setSuppressionList(args.suppress_rules)
if args.file_prefix:
checker.setFilePrefix(args.file_prefix)
if args.quiet:
QUIET = True
if args.no_summary:
@ -2122,4 +2196,7 @@ else:
if SHOW_SUMMARY:
print("\nMISRA rule violations found: %d\n" % (number_of_violations))
if args.show_suppressed_rules:
checker.showSuppressedRules()
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