1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# 4# Copyright 2017, Data61 5# Commonwealth Scientific and Industrial Research Organisation (CSIRO) 6# ABN 41 687 119 230. 7# 8# This software may be distributed and modified according to the terms of 9# the BSD 2-Clause license. Note that NO WARRANTY is provided. 10# See "LICENSE_BSD2.txt" for details. 11# 12# @TAG(DATA61_BSD) 13# 14 15''' 16A Goanna wrapper to aid in suppressing false positives. 17 18The Goanna static analysis tool is an excellent way of finding bugs in C code, 19but it has no systematic way of suppressing false positives in the command line 20tool. This wrapper script provides support for C code annotations to suppress 21particular classes of warnings in order to achieve this. The C code annotations 22are expected to be formulated as multiline-style comments containing 23"goanna: suppress=" followed by a comma-separated list of warnings you want to 24suppress for that line. For example, if goannacc emitted the following 25warnings: 26 27 foo.c:10: warning: Goanna [ARR-inv-index] Severity-High, ... 28 foo.c:10: warning: Goanna [MEM-leak-alias] Severity-Medium, ... 29 30and you had examined your source code and confirmed that these are false 31positives, you could suppress them with the following comment on line 10: 32 33 return a[x]; /* goanna: suppress=ARR-inv-index,MEM-leak-alias */ 34''' 35 36import re, subprocess, sys 37 38def execute(args, stdout): 39 p = subprocess.Popen(args, stdout=stdout, stderr=subprocess.PIPE, 40 universal_newlines=True) 41 _, stderr = p.communicate() 42 return p.returncode, stderr 43 44def main(argv, out, err): 45 46 # Run Goanna. 47 try: 48 ret, stderr = execute(['goannacc'] + argv[1:], out) 49 except OSError: 50 err.write('goannacc not found\n') 51 return -1 52 53 if ret != 0: 54 # Compilation failed. Don't bother trying to suppress warnings. 55 err.write(stderr) 56 return ret 57 58 # A regex that matches lines of output from Goanna that represent warnings. 59 # See section 10.7 of the Goanna user guide. 60 warning_line = re.compile(r'(?P<relfile>[^\s]+):(?P<lineno>\d+):' 61 r'\s*warning:\s*Goanna\[(?P<checkname>[^\]]+)\]\s*Severity-' 62 r'(?P<severity>\w+),\s*(?P<message>[^\.]*)\.\s*(?P<rules>.*)$') 63 64 # A special formatted comment, instructing us to suppress certain warnings. 65 suppression_mark = re.compile( 66 r'/\*\s*goanna:\s*suppress\s*=\s*(?P<checks>.*?)\s*\*/') 67 68 for line in stderr.split('\n')[:-1]: 69 70 m = warning_line.match(line) 71 72 if m is not None: 73 # This line is a warning. 74 75 relfile = m.group('relfile') 76 lineno = int(m.group('lineno')) 77 checkname = m.group('checkname') 78 79 # Find the source line that triggered this warning. 80 # XXX: Given we may be repeatedly opening and searching the same 81 # file, it might make sense to retain open file handles in a cache, 82 # but for now just close each file after dealing with the current 83 # line. 84 source_line = None 85 try: 86 with open(relfile, 'rt') as f: 87 for index, l in enumerate(f): 88 if index + 1 == lineno: 89 source_line = l 90 break 91 except IOError: 92 # Source file not found. 93 pass 94 95 if source_line is not None: 96 97 # Extract the (possible) suppression marker from this line. 98 s = suppression_mark.search(source_line) 99 if s is not None: 100 # This line contains a suppression marker. 101 checks = s.group('checks').split(',') 102 if checkname in checks: 103 # The marker matches this warning; suppress. 104 continue 105 106 # If we reached here, either the output line was not a warning, the 107 # source file was not found, the line number was invalid or there was no 108 # matching suppression marker. Therefore, don't suppress. 109 err.write('%s\n' % line) 110 111 return 0 112 113if __name__ == '__main__': 114 sys.exit(main(sys.argv, sys.stdout, sys.stderr)) 115