diff --git a/docs/README.md b/docs/README.md index 74e4be6541..f4c891da41 100644 --- a/docs/README.md +++ b/docs/README.md @@ -108,6 +108,7 @@ The following tools are supported: | | [Cppcheck](/tools/report-converter/README.md#cppcheck) | | | [Facebook Infer](/tools/report-converter/README.md#facebook-infer) | | | [Coccinelle](/tools/report-converter/README.md#coccinelle) | +| | [Smatch](/tools/report-converter/README.md#smatch) | | **Java** | [SpotBugs](/tools/report-converter/README.md#spotbugs) | | | [Facebook Infer](/tools/report-converter/README.md#fbinfer) | | **Python** | [Pylint](/tools/report-converter/README.md#pylint) | diff --git a/docs/supported_code_analyzers.md b/docs/supported_code_analyzers.md index 9a924d0b12..b1eddceeb6 100644 --- a/docs/supported_code_analyzers.md +++ b/docs/supported_code_analyzers.md @@ -16,6 +16,7 @@ CodeChecker result directory which can be stored to a CodeChecker server. | | [Cppcheck](/tools/report-converter/README.md#cppcheck) | ✓ | | | [Facebook Infer](/tools/report-converter/README.md#fbinfer) | ✓ | | | [Coccinelle](/tools/report-converter/README.md#coccinelle) | ✓ | +| | [Smatch](/tools/report-converter/README.md#smatch) | ✓ | | **Java** | [FindBugs](http://findbugs.sourceforge.net/) | ✗ | | | [SpotBugs](/tools/report-converter/README.md#spotbugs) | ✓ | | | [Facebook Infer](/tools/report-converter/README.md#fbinfer) | ✓ | diff --git a/tools/report-converter/README.md b/tools/report-converter/README.md index c6b1a27593..a308709286 100644 --- a/tools/report-converter/README.md +++ b/tools/report-converter/README.md @@ -21,6 +21,7 @@ a CodeChecker server. * [Pyflakes](#pyflakes) * [Markdownlint](#markdownlint) * [Coccinelle](#coccinelle) +* [Smatch](#smatch) * [License](#license) ## Install guide @@ -80,6 +81,7 @@ optional arguments: Supported analyzers: asan - AddressSanitizer, https://clang.llvm.org/docs/AddressSanitizer.html clang-tidy - Clang Tidy, https://clang.llvm.org/extra/clang-tidy + coccinelle - Coccinelle, https://github.com/coccinelle/coccinelle cppcheck - Cppcheck, http://cppcheck.sourceforge.net eslint - ESLint, https://eslint.org/ fbinfer - Facebook Infer, https://fbinfer.com @@ -88,6 +90,7 @@ Supported analyzers: msan - MemorySanitizer, https://clang.llvm.org/docs/MemorySanitizer.html pyflakes - Pyflakes, https://github.com/PyCQA/pyflakes pylint - Pylint, https://www.pylint.org + smatch - smatch, https://repo.or.cz/w/smatch.git spotbugs - spotbugs, https://spotbugs.github.io tsan - ThreadSanitizer, https://clang.llvm.org/docs/ThreadSanitizer.html tslint - TSLint, https://palantir.github.io/tslint @@ -385,6 +388,7 @@ report-converter -t mdl -o ./codechecker_mdl_reports ./mdl_reports.out # Store Markdownlint reports with CodeChecker. CodeChecker store ./codechecker_mdl_reports -n mdl +``` ## [Coccinelle](https://github.com/coccinelle/coccinelle) [Coccinelle](https://github.com/coccinelle/coccinelle) allows programmers to easily @@ -409,7 +413,30 @@ report-converter -t coccinelle -o ./codechecker_coccinelle_reports ./coccinelle_ # Store the Cocccinelle reports with CodeChecker. CodeChecker store ./codechecker_coccinelle_reports -n coccinelle +``` + +## [Smatch](https://repo.or.cz/w/smatch.git) +[Smatch](https://repo.or.cz/w/smatch.git) is a static analysis tool for C that is used on the kernel. + +The recommended way of running Smatch is to redirect the output to a file and +give this file to the report converter tool. + +The following example shows you how to run Smatch on kernel sources +and store the results found by Smatch to the CodeChecker database. +```sh +# Change Directory to your project +cd path/to/linux/kernel/repository + +# Run Smatch +# Note: The warnings will be stored by default into smatch_warns.txt after executing the following command +path/to/smatch/smatch_scripts/test_kernel.sh + +# Use 'report-converter' to create a CodeChecker report directory from the +# analyzer result of Smatch +report-converter -t smatch -o ./codechecker_smatch_reports ./smatch_warns.txt +# Store the Smatch reports with CodeChecker. +CodeChecker store ./codechecker_smatch_reports -n smatch ``` ## License diff --git a/tools/report-converter/codechecker_report_converter/cli.py b/tools/report-converter/codechecker_report_converter/cli.py index 8a9b558ecd..9cd9a4c0b4 100755 --- a/tools/report-converter/codechecker_report_converter/cli.py +++ b/tools/report-converter/codechecker_report_converter/cli.py @@ -54,6 +54,8 @@ MarkdownlintAnalyzerResult # noqa from codechecker_report_converter.coccinelle.analyzer_result import \ CoccinelleAnalyzerResult # noqa +from codechecker_report_converter.smatch.analyzer_result import \ + SmatchAnalyzerResult # noqa LOG = logging.getLogger('ReportConverter') @@ -89,7 +91,8 @@ class RawDescriptionDefaultHelpFormatter( UBSANAnalyzerResult.TOOL_NAME: UBSANAnalyzerResult, SpotBugsAnalyzerResult.TOOL_NAME: SpotBugsAnalyzerResult, MarkdownlintAnalyzerResult.TOOL_NAME: MarkdownlintAnalyzerResult, - CoccinelleAnalyzerResult.TOOL_NAME: CoccinelleAnalyzerResult + CoccinelleAnalyzerResult.TOOL_NAME: CoccinelleAnalyzerResult, + SmatchAnalyzerResult.TOOL_NAME: SmatchAnalyzerResult } supported_metadata_keys = ["analyzer_command", "analyzer_version"] diff --git a/tools/report-converter/codechecker_report_converter/smatch/__init__.py b/tools/report-converter/codechecker_report_converter/smatch/__init__.py new file mode 100644 index 0000000000..4259749345 --- /dev/null +++ b/tools/report-converter/codechecker_report_converter/smatch/__init__.py @@ -0,0 +1,7 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- diff --git a/tools/report-converter/codechecker_report_converter/smatch/analyzer_result.py b/tools/report-converter/codechecker_report_converter/smatch/analyzer_result.py new file mode 100644 index 0000000000..feb9990687 --- /dev/null +++ b/tools/report-converter/codechecker_report_converter/smatch/analyzer_result.py @@ -0,0 +1,36 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +from codechecker_report_converter.analyzer_result import AnalyzerResult + +from .output_parser import SmatchParser +from ..plist_converter import PlistConverter + + +class SmatchAnalyzerResult(AnalyzerResult): + """ Transform analyzer result of Smatch. """ + + TOOL_NAME = 'smatch' + NAME = 'Smatch' + URL = 'https://repo.or.cz/w/smatch.git' + + def parse(self, analyzer_result): + """ Creates plist files from the given analyzer result to the given + output directory. + """ + parser = SmatchParser(analyzer_result) + + content = self._get_analyzer_result_file_content(analyzer_result) + if not content: + return + + messages = parser.parse_messages(content) + + plist_converter = PlistConverter(self.TOOL_NAME) + plist_converter.add_messages(messages) + return plist_converter.get_plist_results() diff --git a/tools/report-converter/codechecker_report_converter/smatch/output_parser.py b/tools/report-converter/codechecker_report_converter/smatch/output_parser.py new file mode 100644 index 0000000000..ea1c9d588a --- /dev/null +++ b/tools/report-converter/codechecker_report_converter/smatch/output_parser.py @@ -0,0 +1,64 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +import logging +import os +import re + +from ..output_parser import BaseParser, Message +LOG = logging.getLogger('ReportConverter') + + +class SmatchParser(BaseParser): + """ + Parser for Smatch Output + """ + + def __init__(self, analyzer_result): + super(SmatchParser, self).__init__() + + self.analyzer_result = analyzer_result + + self.message_line_re = re.compile( + # File path followed by a ':'. + r'^(?P[\S ]+?):' + # Line number followed by a whitespace. + r'(?P\d+?)\s' + # Function name followed by a whitespace. + r'(?P\S+)\s' + # Checker name followed by a whitespace + r'\[smatch\.(?P\S+)\]\s' + # Message. + r'(?P[\S \t]+)\s*') + + def parse_message(self, it, line): + """ + Actual Parsing function for the given line + It is expected that each line contains a seperate report + """ + match = self.message_line_re.match(line) + if match is None: + return None, next(it) + + file_path = os.path.normpath( + os.path.join(os.path.dirname(self.analyzer_result), + match.group('path'))) + + column = 0 + + message = Message( + file_path, + int(match.group('line')), + column, + match.group('message').strip(), + match.group('checker_name')) + + try: + return message, next(it) + except StopIteration: + return message, '' diff --git a/tools/report-converter/tests/unit/smatch_output_test_files/files/sample.c b/tools/report-converter/tests/unit/smatch_output_test_files/files/sample.c new file mode 100644 index 0000000000..38aee273fb --- /dev/null +++ b/tools/report-converter/tests/unit/smatch_output_test_files/files/sample.c @@ -0,0 +1,5 @@ +static struct parse_smatch *check_sample() { + int res; + res = 0; + return ERR_PTR(res); +} diff --git a/tools/report-converter/tests/unit/smatch_output_test_files/sample.expected.plist b/tools/report-converter/tests/unit/smatch_output_test_files/sample.expected.plist new file mode 100644 index 0000000000..542757098a --- /dev/null +++ b/tools/report-converter/tests/unit/smatch_output_test_files/sample.expected.plist @@ -0,0 +1,69 @@ + + + + + diagnostics + + + category + unknown + check_name + check_zero_to_err_ptr + description + warn: passing zero to 'ERR_PTR' + issue_hash_content_of_line_in_context + 2d8b9711e5b334b518d201a8a6799fdb + location + + col + 0 + file + 0 + line + 4 + + path + + + depth + 0 + kind + event + location + + col + 0 + file + 0 + line + 4 + + message + warn: passing zero to 'ERR_PTR' + + + type + smatch + + + files + + files/sample.c + + metadata + + analyzer + + name + smatch + + generated_by + + name + report-converter + version + x.y.z + + + + diff --git a/tools/report-converter/tests/unit/smatch_output_test_files/sample.out b/tools/report-converter/tests/unit/smatch_output_test_files/sample.out new file mode 100644 index 0000000000..86989adb36 --- /dev/null +++ b/tools/report-converter/tests/unit/smatch_output_test_files/sample.out @@ -0,0 +1 @@ +files/sample.c:4 check_sample() [smatch.check_zero_to_err_ptr] warn: passing zero to 'ERR_PTR' diff --git a/tools/report-converter/tests/unit/test_smatch_parser.py b/tools/report-converter/tests/unit/test_smatch_parser.py new file mode 100644 index 0000000000..f2936b41bc --- /dev/null +++ b/tools/report-converter/tests/unit/test_smatch_parser.py @@ -0,0 +1,79 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- + +""" +This module tests the correctness of the SmatchAnalyzerResult, which +used in sequence transform Smatch output to a plist file. +""" + + +import os +import plistlib +import shutil +import tempfile +import unittest + + +from codechecker_report_converter.smatch.analyzer_result import \ + SmatchAnalyzerResult + + +class SmatchAnalyzerResultTestCase(unittest.TestCase): + """ Test the output of the SmatchAnalyzerResult. """ + + def setUp(self): + """ Setup the test. """ + self.analyzer_result = SmatchAnalyzerResult() + self.cc_result_dir = tempfile.mkdtemp() + self.test_files = os.path.join(os.path.dirname(__file__), + 'smatch_output_test_files') + + def tearDown(self): + """ Clean temporary directory. """ + shutil.rmtree(self.cc_result_dir) + + def test_no_smatch_output_file(self): + """ Test transforming single C file. """ + analyzer_result = os.path.join(self.test_files, 'files', + 'sample.c') + + ret = self.analyzer_result.transform(analyzer_result, + self.cc_result_dir) + self.assertFalse(ret) + + def test_transform_dir(self): + """ Test transforming a directory. """ + analyzer_result = os.path.join(self.test_files) + + ret = self.analyzer_result.transform(analyzer_result, + self.cc_result_dir) + self.assertFalse(ret) + + def test_transform_single_file(self): + """ Test transforming single output file. """ + analyzer_result = os.path.join(self.test_files, 'sample.out') + self.analyzer_result.transform(analyzer_result, self.cc_result_dir) + + plist_file = os.path.join(self.cc_result_dir, + 'sample.c_smatch.plist') + + with open(plist_file, mode='rb') as pfile: + res = plistlib.load(pfile) + + # Use relative path for this test. + res['files'][0] = os.path.join('files', 'sample.c') + + self.assertTrue(res['metadata']['generated_by']['version']) + res['metadata']['generated_by']['version'] = "x.y.z" + + plist_file = os.path.join(self.test_files, + 'sample.expected.plist') + with open(plist_file, mode='rb') as pfile: + exp = plistlib.load(pfile) + + self.assertEqual(res, exp)