From 632494a0f3ad298ee0806c2e795cb229566c8b6b Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 25 May 2017 13:03:43 -0400
Subject: [PATCH 11/38] FIXME: add a flawfinder checker

---
 checkers/Makefile      |   1 +
 checkers/checker.py    |   2 +-
 checkers/flawfinder.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++
 gcc/checkers.c         |   1 +
 4 files changed, 131 insertions(+), 1 deletion(-)
 create mode 100755 checkers/flawfinder.py

diff --git a/checkers/Makefile b/checkers/Makefile
index 79f04e1..cf7599e 100644
--- a/checkers/Makefile
+++ b/checkers/Makefile
@@ -4,3 +4,4 @@ check-all:
 	python checker.py
 	python clang_analyzer.py unittest
 	python cppcheck.py unittest
+	python flawfinder.py unittest
diff --git a/checkers/checker.py b/checkers/checker.py
index 2b6d65f..dc79f17 100755
--- a/checkers/checker.py
+++ b/checkers/checker.py
@@ -391,7 +391,7 @@ class InvokeCpychecker(Tool):
 ############################################################################
 
 class ToolTests(unittest.TestCase):
-    def make_ctxt(self, capture_exceptions=True):
+    def make_ctxt(self, capture_exceptions=False):
         return Context(enable_logging=0, capture_exceptions=capture_exceptions)
 
     def make_tool(self):
diff --git a/checkers/flawfinder.py b/checkers/flawfinder.py
new file mode 100755
index 0000000..5d1aedc
--- /dev/null
+++ b/checkers/flawfinder.py
@@ -0,0 +1,128 @@
+#!/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 sys
+import tempfile
+import unittest
+
+from gccinvocation import GccInvocation
+
+from checker import Tool, Context, ToolTests, make_file, make_stats, \
+    tool_main
+
+from firehose.model import Analysis, Generator, Metadata, Failure, \
+    Location, File, Message, Issue, Trace
+from firehose.parsers.flawfinder import parse_file
+
+class InvokeFlawfinder(Tool):
+    """
+    Tool subclass that invokes "flawfinder"
+    """
+    def __init__(self, ctxt):
+        Tool.__init__(self, 'flawfinder', ctxt)
+
+    def raw_invoke(self, gccinv, sourcefile):
+        args = ['flawfinder', sourcefile] # FIXME
+        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
+
+        if 0:
+            print('result.err: %r' % result.err)
+            print('result.out: %r' % result.out)
+
+        # (there doesn't seem to be a way to have flawfinder directly
+        # save its output to a given location)
+
+        with tempfile.NamedTemporaryFile() as outfile:
+            outfile.write(result.out)
+            outfile.flush()
+
+            with open(outfile.name) as infile:
+                # Parse stderr into firehose XML format and save:
+                analysis = parse_file(infile)
+                analysis.metadata.file_ = make_file(result.sourcefile)
+                analysis.metadata.stats = make_stats(result.timer)
+                self.set_custom_fields(result, analysis)
+
+                return analysis
+
+    def set_custom_fields(self, result, analysis):
+        analysis.set_custom_field('flawfinder-invocation',
+                                  ' '.join(result.argv))
+        result.set_custom_fields(analysis)
+
+class FlawfinderTests(ToolTests):
+    def make_tool(self):
+        ctxt = self.make_ctxt()
+        return InvokeFlawfinder(ctxt)
+
+    def verify_basic_metadata(self, analysis, sourcefile):
+        # Verify basic metadata:
+        self.assert_metadata(analysis, 'flawfinder', sourcefile)
+        self.assert_has_custom_field(analysis, 'flawfinder-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), 0)
+
+    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, 'flawfinder', 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_use_of_random(self):
+        analysis = self.invoke('test-sources/cpychecker-demo.c')
+        self.assertEqual(len(analysis.results), 1)
+        r0 = analysis.results[0]
+        self.assertIsInstance(r0, Issue)
+        self.assertEqual(r0.testid, 'random')
+        self.assertEqual(r0.location.file.givenpath,
+                         'test-sources/cpychecker-demo.c')
+        self.assertEqual(r0.location.point.line, 97)
+        self.assertEqual(r0.message.text,
+                         "This function is not sufficiently random for"
+                         " security-related functions such as key and nonce"
+                         " creation. use a more secure technique for"
+                         " acquiring random values.")
+        self.assertEqual(r0.severity, '3')
+
+if __name__ == '__main__':
+    sys.exit(tool_main(sys.argv, InvokeFlawfinder))
diff --git a/gcc/checkers.c b/gcc/checkers.c
index d92c141..159a166 100644
--- a/gcc/checkers.c
+++ b/gcc/checkers.c
@@ -254,4 +254,5 @@ run_analyzers (const char *path)
 
   run_one_analyzer ("../../src/checkers/clang_analyzer.py");
   run_one_analyzer ("../../src/checkers/cppcheck.py");
+  run_one_analyzer ("../../src/checkers/flawfinder.py");
 }
-- 
1.8.5.3

