Skip to content

Commit f286325

Browse files
whoopsmithdanmar
authored andcommitted
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
1 parent 4dbdc93 commit f286325

6 files changed

Lines changed: 197 additions & 19 deletions

File tree

addons/misra.py

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,30 @@ def generateTable():
521521
sys.exit(1)
522522

523523

524+
def remove_file_prefix(file_path, prefix):
525+
"""
526+
Remove a file path prefix from a give path. leftover
527+
directory seperators at the beginning of a file
528+
after the removal are also stripped.
529+
530+
Example:
531+
'/remove/this/path/file.c'
532+
with a prefix of:
533+
'/remove/this/path'
534+
becomes:
535+
file.c
536+
"""
537+
result = None
538+
if file_path.startswith(prefix):
539+
result = file_path[len(prefix):]
540+
# Remove any leftover directory seperators at the
541+
# beginning
542+
result = result.lstrip('\\/')
543+
else:
544+
result = file_path
545+
return result
546+
547+
524548
class MisraChecker:
525549

526550
def __init__(self):
@@ -543,14 +567,16 @@ def __init__(self):
543567
# Major * 100 + minor. ie Rule 5.2 = (5*100) + 2
544568
# Dict 2 is keyed by filename. An entry of None means suppress globaly.
545569
# Each file name entry contails a list of tuples of (lineNumber, symbolName)
546-
# or None which indicates suppress rule for the entire file.
570+
# or an item of None which indicates suppress rule for the entire file.
547571
# The line and symbol name tuple may have None as either of its elements but
548572
# should not be None for both.
549573
self.suppressedRules = dict()
550574

551575
# List of suppression extracted from the dumpfile
552576
self.dumpfileSuppressions = None
553577

578+
# Prefix to ignore when matching suppression files.
579+
self.filePrefix = None
554580

555581
def misra_3_1(self, rawTokens):
556582
for token in rawTokens:
@@ -1678,13 +1704,17 @@ def addSuppressedRule(self, ruleNum,
16781704
format. The value of that dictionary is a dictionary of filenames.
16791705
If the value is None then the rule is assumed to be suppressed for
16801706
all files.
1681-
If the filename exists then the value of that dictionary contains the
1682-
scope of the suppression. If the value is None then the rule is assumed
1683-
to be suppresed for the entire file. Otherwise the value of the dictionary
1684-
is a list of line number, symbol name tuples.
1707+
If the filename exists then the value of that dictionary contains a list
1708+
with the scope of the suppression. If the list contains an item of None
1709+
then the rule is assumed to be suppresed for the entire file. Otherwise
1710+
the list contains line number, symbol name tuples.
16851711
For each tuple either line number or symbol name can can be none.
16861712
16871713
"""
1714+
normalized_filename = None
1715+
1716+
if fileName is not None:
1717+
normalized_filename = os.path.normpath(fileName)
16881718

16891719
if lineNumber is not None or symbolName is not None:
16901720
line_symbol = (lineNumber, symbolName)
@@ -1697,7 +1727,7 @@ def addSuppressedRule(self, ruleNum,
16971727
ruleItemList.append(line_symbol)
16981728

16991729
fileDict = dict()
1700-
fileDict[fileName] = ruleItemList
1730+
fileDict[normalized_filename] = ruleItemList
17011731

17021732
self.suppressedRules[ruleNum] = fileDict
17031733

@@ -1711,22 +1741,21 @@ def addSuppressedRule(self, ruleNum,
17111741
fileDict = self.suppressedRules[ruleNum]
17121742

17131743
# If the filename is not in the dict already add it
1714-
if not fileName in fileDict:
1744+
if not normalized_filename in fileDict:
17151745
ruleItemList = list()
17161746
ruleItemList.append(line_symbol)
17171747

1718-
fileDict[fileName] = ruleItemList
1748+
fileDict[normalized_filename] = ruleItemList
17191749

17201750
# Rule is added with a file scope. Done
17211751
return
17221752

1723-
# Rule has a matching filename. Check for
1724-
# rule a rule item list.
1753+
# Rule has a matching filename. Get the rule item list.
17251754

1726-
# If it exists then check the lists of rule items
1727-
# to see if the lineNumber, symbonName combination
1728-
# exists
1729-
ruleItemList = fileDict[fileName]
1755+
# Check the lists of rule items
1756+
# to see if this (lineNumber, symbonName) combination
1757+
# or None already exists.
1758+
ruleItemList = fileDict[normalized_filename]
17301759

17311760
if line_symbol is None:
17321761
# is it already in the list?
@@ -1763,8 +1792,15 @@ def isRuleSuppressed(self, location, ruleNum):
17631792
17641793
"""
17651794
ruleIsSuppressed = False
1766-
filename = location.file
1767-
linenr = location.linenr
1795+
linenr = location.linenr
1796+
1797+
# Remove any prefix listed in command arguments from the filename.
1798+
filename = None
1799+
if location.file is not None:
1800+
if self.filePrefix is not None:
1801+
filename = remove_file_prefix(location.file, self.filePrefix)
1802+
else:
1803+
filename = location.file
17681804

17691805
if ruleNum in self.suppressedRules:
17701806
fileDict = self.suppressedRules[ruleNum]
@@ -1780,9 +1816,9 @@ def isRuleSuppressed(self, location, ruleNum):
17801816
# Get the list of ruleItems
17811817
ruleItemList = fileDict[filename]
17821818

1783-
if ruleItemList is None:
1784-
# None for itemRuleList means the rule is suppressed
1785-
# for all lines in the filename
1819+
if None in ruleItemList:
1820+
# Entry of None in the ruleItemList means the rule is
1821+
# suppressed for all lines in the filename
17861822
ruleIsSuppressed = True
17871823
else:
17881824
# Iterate though the the list of line numbers
@@ -1818,6 +1854,39 @@ def parseSuppressions(self):
18181854
each.lineNumber, each.symbolName)
18191855

18201856

1857+
def showSuppressedRules(self):
1858+
"""
1859+
Print out rules in suppression list sorted by Rule Number
1860+
"""
1861+
print("Suppressed Rules List:")
1862+
outlist = list()
1863+
1864+
for ruleNum in self.suppressedRules:
1865+
fileDict = self.suppressedRules[ruleNum]
1866+
1867+
for fname in fileDict:
1868+
ruleItemList = fileDict[fname]
1869+
1870+
for item in ruleItemList:
1871+
if item is None:
1872+
item_str = "None"
1873+
else:
1874+
item_str = str(item[0])
1875+
1876+
outlist.append("%s: %s: %s" % (float(ruleNum)/100,fname,item_str))
1877+
1878+
for line in sorted(outlist, reverse=True):
1879+
print(" %s" % line)
1880+
1881+
1882+
def setFilePrefix(self, prefix):
1883+
"""
1884+
Set the file prefix to ignnore from files when matching
1885+
supression files
1886+
"""
1887+
self.filePrefix = prefix
1888+
1889+
18211890
def setSuppressionList(self, suppressionlist):
18221891
num1 = 0
18231892
num2 = 0
@@ -2064,6 +2133,8 @@ def parseDump(self, dumpfile):
20642133
parser.add_argument("-verify", help=argparse.SUPPRESS, action="store_true")
20652134
parser.add_argument("-generate-table", help=argparse.SUPPRESS, action="store_true")
20662135
parser.add_argument("dumpfile", nargs='*', help="Path of dump file from cppcheck")
2136+
parser.add_argument("--show-suppressed-rules", help="Print rule suppression list", action="store_true")
2137+
parser.add_argument("-P", "--file-prefix", type=str, help="Prefix to strip when matching suppression file rules")
20672138
args = parser.parse_args()
20682139

20692140
checker = MisraChecker()
@@ -2083,6 +2154,9 @@ def parseDump(self, dumpfile):
20832154
if args.suppress_rules:
20842155
checker.setSuppressionList(args.suppress_rules)
20852156

2157+
if args.file_prefix:
2158+
checker.setFilePrefix(args.file_prefix)
2159+
20862160
if args.quiet:
20872161
QUIET = True
20882162
if args.no_summary:
@@ -2122,4 +2196,7 @@ def parseDump(self, dumpfile):
21222196
if SHOW_SUMMARY:
21232197
print("\nMISRA rule violations found: %d\n" % (number_of_violations))
21242198

2199+
if args.show_suppressed_rules:
2200+
checker.showSuppressedRules()
2201+
21252202
sys.exit(exitCode)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// To test:
2+
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
3+
// There should be no violations reported
4+
5+
// This needs to stay at line number 7 to make the test pass
6+
// If it is changed update suppressions.txt with the new line number
7+
#include <stdio.h> //21.6
8+
9+
extern int misra_5_2_var_hides_var______31x;
10+
static int misra_5_2_var_hides_var______31y;//5.2
11+
static int misra_5_2_function_hides_var_31x;
12+
void misra_5_2_function_hides_var_31y(void) {}//5.2
13+
void foo(void)
14+
{
15+
int i;
16+
switch(misra_5_2_func1()) //16.4 16.6
17+
{
18+
case 1:
19+
{
20+
do
21+
{
22+
for(i = 0; i < 10; i++)
23+
{
24+
if(misra_5_2_func3()) //14.4
25+
{
26+
int misra_5_2_var_hides_var_1____31x;
27+
int misra_5_2_var_hides_var_1____31y;//5.2
28+
}
29+
}
30+
} while(misra_5_2_func2()); //14.4
31+
}
32+
}
33+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// To test:
2+
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
3+
// There should be no violations reported
4+
5+
union misra_5_2_field_hides_field__63x { //19.2
6+
int misra_5_2_field_hides_field__31x;
7+
int misra_5_2_field_hides_field__31y;//5.2
8+
};
9+
struct misra_5_2_field_hides_field__63y { //5.2
10+
int misra_5_2_field_hides_field1_31x;
11+
int misra_5_2_field_hides_field1_31y;//5.2
12+
};
13+
const char *s41_1 = "\x41g"; // 4.1
14+
const char *s41_2 = "\x41\x42";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// To test:
2+
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
3+
// There should be no violations reported
4+
5+
// This needs to stay at line number 7 to make the test pass
6+
// If it is changed update suppressions.txt with the new line number
7+
#include <stdio.h> //21.6
8+
9+
extern int misra_5_2_var_hides_var______31x;
10+
static int misra_5_2_var_hides_var______31y;//5.2
11+
static int misra_5_2_function_hides_var_31x;
12+
void misra_5_2_function_hides_var_31y(void) {}//5.2
13+
void foo(void)
14+
{
15+
int i;
16+
switch(misra_5_2_func1()) //16.4 16.6
17+
{
18+
case 1:
19+
{
20+
do
21+
{
22+
for(i = 0; i < 10; i++)
23+
{
24+
if(misra_5_2_func3()) //14.4
25+
{
26+
int misra_5_2_var_hides_var_1____31x;
27+
int misra_5_2_var_hides_var_1____31y;//5.2
28+
}
29+
}
30+
} while(misra_5_2_func2()); //14.4
31+
}
32+
}
33+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// To test:
2+
// ../../cppcheck --suppressions-list=suppressions.txt --dump misra-suppressions*-test.c && python ../misra.py misra-suppressions*-test.c.dump
3+
// There should be no violations reported
4+
5+
union misra_5_2_field_hides_field__63x { //19.2
6+
int misra_5_2_field_hides_field__31x;
7+
int misra_5_2_field_hides_field__31y;//5.2
8+
};
9+
struct misra_5_2_field_hides_field__63y { //5.2
10+
int misra_5_2_field_hides_field1_31x;
11+
int misra_5_2_field_hides_field1_31y;//5.2
12+
};
13+
const char *s41_1 = "\x41g"; // 4.1
14+
const char *s41_2 = "\x41\x42";

addons/test/suppressions.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
misra_21.6:misra-suppressions1-test.c:7
2+
misra_14_4
3+
misra.5.2
4+
MISRA_16_4:misra-suppressions1-test.c
5+
MISRA.16.6:misra-suppressions1-test.c
6+
MISRA_4_1:misra-suppressions2-test.c
7+
MISRA.19_2:misra-suppressions2-test.c

0 commit comments

Comments
 (0)