blob: 7c8e975c43993db60a2d61f36c28f1e987800d78 [file] [log] [blame] [edit]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
#
# SPDX-License-Identifier: BSD-2-Clause
#
'''
A Goanna wrapper to aid in suppressing false positives.
The Goanna static analysis tool is an excellent way of finding bugs in C code,
but it has no systematic way of suppressing false positives in the command line
tool. This wrapper script provides support for C code annotations to suppress
particular classes of warnings in order to achieve this. The C code annotations
are expected to be formulated as multiline-style comments containing
"goanna: suppress=" followed by a comma-separated list of warnings you want to
suppress for that line. For example, if goannacc emitted the following
warnings:
foo.c:10: warning: Goanna [ARR-inv-index] Severity-High, ...
foo.c:10: warning: Goanna [MEM-leak-alias] Severity-Medium, ...
and you had examined your source code and confirmed that these are false
positives, you could suppress them with the following comment on line 10:
return a[x]; /* goanna: suppress=ARR-inv-index,MEM-leak-alias */
'''
import re, subprocess, sys
def execute(args, stdout):
p = subprocess.Popen(args, stdout=stdout, stderr=subprocess.PIPE,
universal_newlines=True)
_, stderr = p.communicate()
return p.returncode, stderr
def main(argv, out, err):
# Run Goanna.
try:
ret, stderr = execute(['goannacc'] + argv[1:], out)
except OSError:
err.write('goannacc not found\n')
return -1
if ret != 0:
# Compilation failed. Don't bother trying to suppress warnings.
err.write(stderr)
return ret
# A regex that matches lines of output from Goanna that represent warnings.
# See section 10.7 of the Goanna user guide.
warning_line = re.compile(r'(?P<relfile>[^\s]+):(?P<lineno>\d+):'
r'\s*warning:\s*Goanna\[(?P<checkname>[^\]]+)\]\s*Severity-'
r'(?P<severity>\w+),\s*(?P<message>[^\.]*)\.\s*(?P<rules>.*)$')
# A special formatted comment, instructing us to suppress certain warnings.
suppression_mark = re.compile(
r'/\*\s*goanna:\s*suppress\s*=\s*(?P<checks>.*?)\s*\*/')
for line in stderr.split('\n')[:-1]:
m = warning_line.match(line)
if m is not None:
# This line is a warning.
relfile = m.group('relfile')
lineno = int(m.group('lineno'))
checkname = m.group('checkname')
# Find the source line that triggered this warning.
# XXX: Given we may be repeatedly opening and searching the same
# file, it might make sense to retain open file handles in a cache,
# but for now just close each file after dealing with the current
# line.
source_line = None
try:
with open(relfile, 'rt') as f:
for index, l in enumerate(f):
if index + 1 == lineno:
source_line = l
break
except IOError:
# Source file not found.
pass
if source_line is not None:
# Extract the (possible) suppression marker from this line.
s = suppression_mark.search(source_line)
if s is not None:
# This line contains a suppression marker.
checks = s.group('checks').split(',')
if checkname in checks:
# The marker matches this warning; suppress.
continue
# If we reached here, either the output line was not a warning, the
# source file was not found, the line number was invalid or there was no
# matching suppression marker. Therefore, don't suppress.
err.write('%s\n' % line)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv, sys.stdout, sys.stderr))