Index: clang/tools/scan-build-py/libscanbuild/analyze.py =================================================================== --- clang/tools/scan-build-py/libscanbuild/analyze.py +++ clang/tools/scan-build-py/libscanbuild/analyze.py @@ -52,7 +52,8 @@ args = parse_args_for_scan_build() # will re-assign the report directory as new output - with report_directory(args.output, args.keep_empty) as args.output: + with report_directory( + args.output, args.keep_empty, args.output_format) as args.output: # Run against a build command. there are cases, when analyzer run # is not required. But we need to set up everything for the # wrappers, because 'configure' needs to capture the CC/CXX values @@ -79,7 +80,7 @@ args = parse_args_for_analyze_build() # will re-assign the report directory as new output - with report_directory(args.output, args.keep_empty) as args.output: + with report_directory(args.output, args.keep_empty, args.output_format) as args.output: # Run the analyzer against a compilation db. govern_analyzer_runs(args) # Cover report generation and bug counting. @@ -336,7 +337,7 @@ @contextlib.contextmanager -def report_directory(hint, keep): +def report_directory(hint, keep, output_format): """ Responsible for the report directory. hint -- could specify the parent directory of the output directory. @@ -355,7 +356,11 @@ yield name finally: if os.listdir(name): - msg = "Run 'scan-view %s' to examine bug reports." + if output_format != 'sarif': + # 'scan-view' currently does not support sarif format. + msg = "Run 'scan-view %s' to examine bug reports." + else: + msg = "View result at %s/result.sarif." keep = True else: if keep: @@ -433,7 +438,7 @@ 'direct_args', # arguments from command line 'force_debug', # kill non debug macros 'output_dir', # where generated report files shall go - 'output_format', # it's 'plist', 'html', both or plist-multi-file + 'output_format', # it's 'plist', 'html', 'plist-html', 'plist-multi-file', or 'sarif' 'output_failures', # generate crash reports or not 'ctu']) # ctu control options def run(opts): @@ -537,6 +542,8 @@ dir=opts['output_dir']) os.close(handle) return name + elif opts['output_format'] == 'sarif': + return os.path.join(opts['output_dir'], 'result.sarif') return opts['output_dir'] try: Index: clang/tools/scan-build-py/libscanbuild/arguments.py =================================================================== --- clang/tools/scan-build-py/libscanbuild/arguments.py +++ clang/tools/scan-build-py/libscanbuild/arguments.py @@ -244,6 +244,14 @@ action='store_const', help="""Cause the results as a set of .plist files with extra information on related files.""") + format_group.add_argument( + '--sarif', + '-sarif', + dest='output_format', + const='sarif', + default='html', + action='store_const', + help="""Cause the results as a result.sarif file.""") advanced = parser.add_argument_group('advanced options') advanced.add_argument( Index: clang/tools/scan-build-py/tests/unit/test_analyze.py =================================================================== --- clang/tools/scan-build-py/tests/unit/test_analyze.py +++ clang/tools/scan-build-py/tests/unit/test_analyze.py @@ -128,7 +128,7 @@ class RunAnalyzerTest(unittest.TestCase): @staticmethod - def run_analyzer(content, failures_report): + def run_analyzer(content, failures_report, output_format='plist'): with libear.TemporaryDirectory() as tmpdir: filename = os.path.join(tmpdir, 'test.cpp') with open(filename, 'w') as handle: @@ -141,31 +141,41 @@ 'direct_args': [], 'file': filename, 'output_dir': tmpdir, - 'output_format': 'plist', + 'output_format': output_format, 'output_failures': failures_report } spy = Spy() result = sut.run_analyzer(opts, spy.call) - return (result, spy.arg) + output_files = [] + for entry in os.listdir(tmpdir): + output_files.append(entry) + return (result, spy.arg, output_files) def test_run_analyzer(self): content = "int div(int n, int d) { return n / d; }" - (result, fwds) = RunAnalyzerTest.run_analyzer(content, False) + (result, fwds, _) = RunAnalyzerTest.run_analyzer(content, False) self.assertEqual(None, fwds) self.assertEqual(0, result['exit_code']) def test_run_analyzer_crash(self): content = "int div(int n, int d) { return n / d }" - (result, fwds) = RunAnalyzerTest.run_analyzer(content, False) + (result, fwds, _) = RunAnalyzerTest.run_analyzer(content, False) self.assertEqual(None, fwds) self.assertEqual(1, result['exit_code']) def test_run_analyzer_crash_and_forwarded(self): content = "int div(int n, int d) { return n / d }" - (_, fwds) = RunAnalyzerTest.run_analyzer(content, True) + (_, fwds, _) = RunAnalyzerTest.run_analyzer(content, True) self.assertEqual(1, fwds['exit_code']) self.assertTrue(len(fwds['error_output']) > 0) + def test_run_analyzer_with_sarif(self): + content = "int div(int n, int d) { return n / d; }" + (result, fwds, output_files) = RunAnalyzerTest.run_analyzer(content, False, output_format='sarif') + self.assertEqual(None, fwds) + self.assertEqual(0, result['exit_code']) + self.assertIn('result.sarif', output_files) + class ReportFailureTest(unittest.TestCase):