This patch adds a harness for invoking cppcheck: http://cppcheck.sourceforge.net/ returning the results in JSON format.
It runs "cppcheck --xml --xml-version=2", then uses firehose.parsers.cppcheck.parse_file to parse the generated .xml file, turning it into firehose JSON. checkers/ChangeLog: * cppcheck.py: New file. --- checkers/cppcheck.py | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100755 checkers/cppcheck.py diff --git a/checkers/cppcheck.py b/checkers/cppcheck.py new file mode 100755 index 0000000..9b6a864 --- /dev/null +++ b/checkers/cppcheck.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# Copyright 2012, 2013, 2015, 2017 David Malcolm <dmalc...@redhat.com> +# Copyright 2012, 2013, 2015, 2017 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. + +import sys +import tempfile + +from firehose.model import Failure, Issue +from firehose.parsers.cppcheck import parse_file +from gccinvocation import GccInvocation + +from checker import Checker, CheckerTests, make_file, make_stats, \ + tool_main + +class InvokeCppcheck(Checker): + """ + Checker subclass that invokes "cppcheck" + """ + name = 'cppcheck' + + def raw_invoke(self, gccinv, sourcefile): + args = ['cppcheck', + '--xml', '--xml-version=2', + sourcefile] + return self._run_subprocess(sourcefile, args) + + def handle_output(self, result): + if result.returncode: + analysis = self._make_failed_analysis(result.sourcefile, result.timer, + msgtext='Bad exit code running %s' % self.name, + failureid='bad-exit-code') + self.set_custom_fields(result, analysis) + return analysis + + # (there doesn't seem to be a way to have cppcheck directly + # save its XML output to a given location) + + with tempfile.NamedTemporaryFile() as outfile: + outfile.write(result.err) + outfile.flush() + + with open(outfile.name) as infile: + # Parse stderr into firehose XML format and save: + analysis = parse_file(infile, + file_=make_file(result.sourcefile), + stats=make_stats(result.timer)) + self.set_custom_fields(result, analysis) + return analysis + + def set_custom_fields(self, result, analysis): + analysis.set_custom_field('cppcheck-invocation', + ' '.join(result.argv)) + result.set_custom_fields(analysis) + +class CppcheckTests(CheckerTests): + def make_tool(self): + return self.make_tool_from_class(InvokeCppcheck) + + def verify_basic_metadata(self, analysis, sourcefile): + # Verify basic metadata: + self.assert_metadata(analysis, 'cppcheck', sourcefile) + self.assert_has_custom_field(analysis, 'cppcheck-invocation') + self.assert_has_custom_field(analysis, 'stdout') + self.assert_has_custom_field(analysis, 'stderr') + + def test_file_not_found(self): + analysis = self.invoke('does-not-exist.c') + self.assertEqual(len(analysis.results), 1) + self.assertIsInstance(analysis.results[0], Failure) + self.assertEqual(analysis.results[0].failureid, 'bad-exit-code') + + def test_timeout(self): + sourcefile = 'test-sources/harmless.c' + tool = self.make_tool() + tool.timeout = 0 + gccinv = GccInvocation(['gcc', sourcefile]) + analysis = tool.checked_invoke(gccinv, sourcefile) + self.assert_metadata(analysis, 'cppcheck', sourcefile) + self.assertEqual(len(analysis.results), 1) + r0 = analysis.results[0] + self.assertIsInstance(r0, Failure) + self.assertEqual(r0.failureid, 'timeout') + self.assert_has_custom_field(analysis, 'timeout') + self.assert_has_custom_field(analysis, 'command-line') + + def test_harmless_file(self): + analysis = self.invoke('test-sources/harmless.c') + self.assertEqual(len(analysis.results), 0) + + def test_read_through_null(self): + analysis = self.invoke('test-sources/read-through-null.c') + self.assertEqual(len(analysis.results), 1) + r0 = analysis.results[0] + self.assertIsInstance(r0, Issue) + self.assertEqual(r0.testid, 'nullPointer') + self.assertEqual(r0.location.file.givenpath, + 'test-sources/read-through-null.c') + self.assertEqual(r0.location.point.line, 3) + self.assertEqual(r0.message.text, + "Null pointer dereference") + self.assertEqual(r0.severity, 'error') + + def test_out_of_bounds(self): + analysis = self.invoke('test-sources/out-of-bounds.c') + self.assertEqual(len(analysis.results), 2) + + r0 = analysis.results[0] + self.assertIsInstance(r0, Issue) + self.assertEqual(r0.testid, 'arrayIndexOutOfBounds') + self.assertEqual(r0.location.file.givenpath, + 'test-sources/out-of-bounds.c') + self.assertEqual(r0.location.point.line, 5) + self.assertEqual( + r0.message.text, + "Array 'arr[10]' accessed at index 15, which is out of bounds.") + self.assertEqual(r0.severity, 'error') + + r1 = analysis.results[1] + self.assertIsInstance(r1, Issue) + self.assertEqual(r1.testid, 'uninitvar') + # etc + +if __name__ == '__main__': + sys.exit(tool_main(sys.argv, InvokeCppcheck)) -- 1.8.5.3