From f286325cec7bc68a4e500d8a225848679133fd8a Mon Sep 17 00:00:00 2001 From: "Richard A. Smith" Date: Thu, 18 Oct 2018 03:17:57 -0400 Subject: [PATCH] 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 --- addons/misra.py | 115 ++++++++++++++++--- addons/test/misra-suppressions1-test.c | 33 ++++++ addons/test/misra-suppressions2-test.c | 14 +++ addons/test/path1/misra-suppressions1-test.c | 33 ++++++ addons/test/path1/misra-suppressions2-test.c | 14 +++ addons/test/suppressions.txt | 7 ++ 6 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 addons/test/misra-suppressions1-test.c create mode 100644 addons/test/misra-suppressions2-test.c create mode 100644 addons/test/path1/misra-suppressions1-test.c create mode 100644 addons/test/path1/misra-suppressions2-test.c create mode 100644 addons/test/suppressions.txt diff --git a/addons/misra.py b/addons/misra.py index 19f5abab8..c61f561c9 100755 --- a/addons/misra.py +++ b/addons/misra.py @@ -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) diff --git a/addons/test/misra-suppressions1-test.c b/addons/test/misra-suppressions1-test.c new file mode 100644 index 000000000..59c77598e --- /dev/null +++ b/addons/test/misra-suppressions1-test.c @@ -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 //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 + } + } +} diff --git a/addons/test/misra-suppressions2-test.c b/addons/test/misra-suppressions2-test.c new file mode 100644 index 000000000..7fadf1869 --- /dev/null +++ b/addons/test/misra-suppressions2-test.c @@ -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"; diff --git a/addons/test/path1/misra-suppressions1-test.c b/addons/test/path1/misra-suppressions1-test.c new file mode 100644 index 000000000..59c77598e --- /dev/null +++ b/addons/test/path1/misra-suppressions1-test.c @@ -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 //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 + } + } +} diff --git a/addons/test/path1/misra-suppressions2-test.c b/addons/test/path1/misra-suppressions2-test.c new file mode 100644 index 000000000..7fadf1869 --- /dev/null +++ b/addons/test/path1/misra-suppressions2-test.c @@ -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"; diff --git a/addons/test/suppressions.txt b/addons/test/suppressions.txt new file mode 100644 index 000000000..acac96847 --- /dev/null +++ b/addons/test/suppressions.txt @@ -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