new file mode 100755
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+# Copyright 2012, 2013, 2015, 2017 David Malcolm <dmalcolm@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 glob
+import os
+import sys
+import tempfile
+
+from gccinvocation import GccInvocation
+from firehose.model import Failure, Issue, Trace
+from firehose.parsers.clanganalyzer import parse_plist
+
+from checker import Checker, CheckerTests, make_file, make_stats, \
+ tool_main
+
+class InvokeClangAnalyzer(Checker):
+ """
+ Checker subclass that invokes the clang analyzer
+ """
+ name = 'clang-analyzer'
+
+ def raw_invoke(self, gccinv, sourcefile):
+ self.resultdir = tempfile.mkdtemp()
+ args = ['scan-build', '-v', '-plist',
+ '--use-analyzer', '/usr/bin/clang', # rhbz 923834
+ '-o', self.resultdir,
+ 'gcc'] + gccinv.argv[1:]
+ 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
+
+ # Given e.g. resultdir='/tmp/tmpQW2l2B', the plist files
+ # are an extra level deep e.g.:
+ # '/tmp/tmpQW2l2B/2013-01-22-1/report-MlwJri.plist'
+ self.log(self.resultdir)
+ for plistpath in glob.glob(os.path.join(self.resultdir,
+ '*/*.plist')):
+ analysis = parse_plist(plistpath,
+ file_=make_file(result.sourcefile),
+ stats=make_stats(result.timer))
+ self.set_custom_fields(result, analysis)
+ analysis.set_custom_field('plistpath', plistpath)
+ return analysis # could there be more than one?
+
+ # Not found?
+ analysis = self._make_failed_analysis(
+ result.sourcefile, result.timer,
+ msgtext='Unable to locate plist file',
+ failureid='plist-not-found')
+ self.set_custom_fields(result, analysis)
+ return analysis
+
+ def set_custom_fields(self, result, analysis):
+ analysis.set_custom_field('scan-build-invocation',
+ ' '.join(result.argv))
+ result.set_custom_fields(analysis)
+
+class ClangAnalyzerTests(CheckerTests):
+ def make_tool(self):
+ return self.make_tool_from_class(InvokeClangAnalyzer)
+
+ def verify_basic_metadata(self, analysis, sourcefile):
+ # Verify basic metadata:
+ self.assert_metadata(analysis, 'clang-analyzer', sourcefile)
+ self.assert_has_custom_field(analysis, 'scan-build-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')
+ #print(analysis)
+ 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, 'clang-analyzer', 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, 'Dereference of null pointer')
+ self.assertEqual(r0.location.file.givenpath,
+ 'test-sources/read-through-null.c')
+ self.assertEqual(r0.location.point.line, 3)
+ self.assertEqual(r0.message.text,
+ "Dereference of null pointer")
+ self.assertEqual(r0.severity, None)
+ self.assertIsInstance(r0.trace, Trace)
+
+ def test_out_of_bounds(self):
+ analysis = self.invoke('test-sources/out-of-bounds.c')
+ self.assertEqual(len(analysis.results), 1)
+
+ r0 = analysis.results[0]
+ self.assertIsInstance(r0, Issue)
+ self.assertEqual(r0.testid, 'Garbage return value')
+ self.assertEqual(r0.location.file.givenpath,
+ 'test-sources/out-of-bounds.c')
+ self.assertEqual(r0.location.point.line, 5)
+ self.assertEqual(r0.message.text,
+ "Undefined or garbage value returned to caller")
+ self.assertEqual(r0.severity, None)
+ self.assertIsInstance(r0.trace, Trace)
+
+if __name__ == '__main__':
+ sys.exit(tool_main(sys.argv, InvokeClangAnalyzer))