From 7c0238c70926b6262030cc6870af48ee07c9da5d Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Mon, 30 Sep 2024 14:01:48 +0300 Subject: [PATCH 01/11] PVS-Studio support Was added support for PVS-Studio JSON report format in CodeChecker report converter --- .../analyzers/pvs_studio/__init__.py | 0 .../analyzers/pvs_studio/analyzer_result.py | 66 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py create mode 100644 tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py new file mode 100644 index 0000000000..912a2c4373 --- /dev/null +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py @@ -0,0 +1,66 @@ +# ------------------------------------------------------------------------- +# +# 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 +from typing import List + +from codechecker_report_converter.report import Report, get_or_create_file, File + +from typing import Dict +import os +import json +from ..analyzer_result import AnalyzerResultBase + +LOG = logging.getLogger('report-converter') + + +class AnalyzerResult(AnalyzerResultBase): + """ Transform analyzer result of the PVS-Studio analyzer. """ + + TOOL_NAME = 'pvs-studio' + NAME = 'PVS-Studio' + URL = 'https://pvs-studio.com/en/' + + LEVELS_XML_URL = "https://files.pvs-studio.com/rules/RulesMap.xml" + + def get_reports(self, file_path: str) -> List[Report]: + """ Get reports from the PVS-Studio analyzer result. """ + + reports: List[Report] = [] + + if not os.path.exists(file_path): + LOG.error("Report file does not exist: %s", file_path) + return reports + + try: + with open(file_path, "r", encoding="UTF-8", errors="ignore") as report_file: + bugs = json.load(report_file)['warnings'] + except (IOError, json.decoder.JSONDecodeError): + LOG.error("Failed to parse the given analyzer result '%s'. Please " + "give a valid json file generated by PVS-Studio.", + file_path) + return reports + + file_cache: Dict[str, File] = {} + for bug in bugs: + bug_positions = bug['positions'] + + for position in bug_positions: + if not os.path.exists(position['file']): + LOG.error("Source file does not exist: %s", position['file']) + continue + + reports.append(Report( + get_or_create_file(os.path.abspath(position['file']), file_cache), + position['line'], + position['column'] if position.get('column') else 0, + bug['message'], + bug['code'], + )) + + return reports From 9f8ad8bb20512b782ccf53a2870fc2accad0e580 Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Mon, 30 Sep 2024 14:06:43 +0300 Subject: [PATCH 02/11] Delete RulesMap.xml --- .../analyzers/pvs_studio/analyzer_result.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py index 912a2c4373..0e6465d742 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py @@ -26,8 +26,6 @@ class AnalyzerResult(AnalyzerResultBase): NAME = 'PVS-Studio' URL = 'https://pvs-studio.com/en/' - LEVELS_XML_URL = "https://files.pvs-studio.com/rules/RulesMap.xml" - def get_reports(self, file_path: str) -> List[Report]: """ Get reports from the PVS-Studio analyzer result. """ From fcb4f4155ee6fa15d2c7b8e395ab505026ba6b0a Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Mon, 30 Sep 2024 16:14:08 +0300 Subject: [PATCH 03/11] PVS-Studio report's tests --- .../files/sample.cpp | 13 ++++ .../pvs_studio_output_test_files/sample.json | 27 ++++++++ .../pvs_studio_output_test_files/sample.plist | 69 +++++++++++++++++++ .../unit/analyzers/test_pvs_studio_parser.py | 63 +++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/files/sample.cpp create mode 100644 tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.json create mode 100644 tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.plist create mode 100644 tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py diff --git a/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/files/sample.cpp b/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/files/sample.cpp new file mode 100644 index 0000000000..d7d878e754 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/files/sample.cpp @@ -0,0 +1,13 @@ +#include "stdafx.h" + +int main() +{ + int a = 5; + int b = 6; + + if (a < b) { + return 1; + } + + return 0; +} diff --git a/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.json b/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.json new file mode 100644 index 0000000000..d497858686 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.json @@ -0,0 +1,27 @@ +{ + "version": 2, + "warnings": [ + { + "code": "V547", + "cwe": 571, + "level": 1, + "positions": [ + { + "file": "files/sample.cpp", + "line": 8, + "endLine": 8, + "navigation": { + "previousLine": 4979, + "currentLine": 11857, + "nextLine": 11235, + "columns": 0 + } + } + ], + "projects": [], + "message": "Expression 'a < b' is always true.", + "favorite": false, + "falseAlarm": false + } + ] +} diff --git a/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.plist b/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.plist new file mode 100644 index 0000000000..374b101ea3 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.plist @@ -0,0 +1,69 @@ + + + + + diagnostics + + + category + unknown + check_name + V547 + description + Expression 'a < b' is always true. + issue_hash_content_of_line_in_context + ba476be198a6e52c12427e1d8606baa8 + location + + col + 0 + file + 0 + line + 8 + + path + + + depth + 0 + kind + event + location + + col + 0 + file + 0 + line + 8 + + message + Expression 'a < b' is always true. + + + type + pvs-studio + + + files + + files\sample.cpp + + metadata + + analyzer + + name + pvs-studio + + generated_by + + name + report-converter + version + 0.1.0 + + + + diff --git a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py new file mode 100644 index 0000000000..06eda4d319 --- /dev/null +++ b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py @@ -0,0 +1,63 @@ +import unittest +import tempfile +import shutil +import plistlib +import os + + +from codechecker_report_converter.analyzers.pvs_studio import analyzer_result + +class PvsStudioAnalyzerResultTestCase(unittest.TestCase): + """ Test the output of PVS-Studio's AnalyzerResult. """ + + def setUp(self): + """ Set up the test. """ + self.analyzer_result = analyzer_result.AnalyzerResult() + self.result_dir = tempfile.mkdtemp() + self.test_files = os.path.join(os.path.dirname(__file__), + 'pvs_studio_output_test_files') + + def tearDown(self): + """Clean temporary directory. """ + shutil.rmtree(self.result_dir) + + def test_no_report_output_file(self): + """ Test transforming single cpp file. """ + result = os.path.join(self.test_files, "files", "sample.cpp") + + is_success = self.analyzer_result.transform([result], self.result_dir, file_name="{source_file}_{analyzer}") + + self.assertFalse(is_success) + + def test_transform_dir(self): + """ Test transforming a directory. """ + result = os.path.join(self.test_files) + + is_success = self.analyzer_result.transform( + [analyzer_result], self.result_dir, file_name="{source_file}_{analyzer}") + + self.assertFalse(is_success) + + 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.result_dir, + file_name="{source_file}_{analyzer}") + + plist_file = os.path.join(self.result_dir, + 'sample.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.cpp') + + plist_file = os.path.join(self.test_files, + 'sample.plist') + + with open(plist_file, mode='rb') as pfile: + exp = plistlib.load(pfile) + + self.assertEqual(res, exp) From 7c4c1650a9810811af147212b0d62280eb4ccbc3 Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Tue, 1 Oct 2024 14:16:44 +0300 Subject: [PATCH 04/11] Fix pylint errors --- .../analyzers/pvs_studio/analyzer_result.py | 19 +++++++++++++++---- .../unit/analyzers/test_pvs_studio_parser.py | 13 ++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py index 0e6465d742..37a3be7c5f 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py @@ -9,7 +9,9 @@ import logging from typing import List -from codechecker_report_converter.report import Report, get_or_create_file, File +from codechecker_report_converter.report import (Report, + get_or_create_file, + File) from typing import Dict import os @@ -36,7 +38,10 @@ def get_reports(self, file_path: str) -> List[Report]: return reports try: - with open(file_path, "r", encoding="UTF-8", errors="ignore") as report_file: + with open(file_path, + "r", + encoding="UTF-8", + errors="ignore") as report_file: bugs = json.load(report_file)['warnings'] except (IOError, json.decoder.JSONDecodeError): LOG.error("Failed to parse the given analyzer result '%s'. Please " @@ -50,11 +55,17 @@ def get_reports(self, file_path: str) -> List[Report]: for position in bug_positions: if not os.path.exists(position['file']): - LOG.error("Source file does not exist: %s", position['file']) + LOG.error( + "Source file does not exist: %s", + position['file'] + ) continue reports.append(Report( - get_or_create_file(os.path.abspath(position['file']), file_cache), + get_or_create_file( + os.path.abspath(position['file']), + file_cache + ), position['line'], position['column'] if position.get('column') else 0, bug['message'], diff --git a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py index 06eda4d319..f8e50f8a39 100644 --- a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py +++ b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py @@ -7,6 +7,7 @@ from codechecker_report_converter.analyzers.pvs_studio import analyzer_result + class PvsStudioAnalyzerResultTestCase(unittest.TestCase): """ Test the output of PVS-Studio's AnalyzerResult. """ @@ -25,7 +26,10 @@ def test_no_report_output_file(self): """ Test transforming single cpp file. """ result = os.path.join(self.test_files, "files", "sample.cpp") - is_success = self.analyzer_result.transform([result], self.result_dir, file_name="{source_file}_{analyzer}") + is_success = self.analyzer_result.transform( + [result], self.result_dir, + file_name="{source_file}_{analyzer}" + ) self.assertFalse(is_success) @@ -34,8 +38,11 @@ def test_transform_dir(self): result = os.path.join(self.test_files) is_success = self.analyzer_result.transform( - [analyzer_result], self.result_dir, file_name="{source_file}_{analyzer}") - + [result], + self.result_dir, + file_name="{source_file}_{analyzer}" + ) + self.assertFalse(is_success) def test_transform_single_file(self): From 56202e83d4e0dd128b19c02e0e79ba7d917cd40a Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Tue, 1 Oct 2024 15:41:46 +0300 Subject: [PATCH 05/11] E1120 fix and PVS-Studio's diagnostics' dynamic severity --- .../analyzers/pvs_studio/analyzer_result.py | 6 ++++++ .../unit/analyzers/test_pvs_studio_parser.py | 20 ++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py index 37a3be7c5f..5276a89b0d 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py @@ -28,6 +28,8 @@ class AnalyzerResult(AnalyzerResultBase): NAME = 'PVS-Studio' URL = 'https://pvs-studio.com/en/' + __severities = ["UNSPECIFIED", "HIGH", "MEDIUM", "LOW"] + def get_reports(self, file_path: str) -> List[Report]: """ Get reports from the PVS-Studio analyzer result. """ @@ -70,6 +72,10 @@ def get_reports(self, file_path: str) -> List[Report]: position['column'] if position.get('column') else 0, bug['message'], bug['code'], + severity=self.get_diagnostic_severity(bug.get('level')) )) return reports + + def get_diagnostic_severity(self, level: int) -> str: + return self.__severities[level] diff --git a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py index f8e50f8a39..60f4a48265 100644 --- a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py +++ b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py @@ -6,50 +6,56 @@ from codechecker_report_converter.analyzers.pvs_studio import analyzer_result +from codechecker_report_converter.report.parser import plist class PvsStudioAnalyzerResultTestCase(unittest.TestCase): """ Test the output of PVS-Studio's AnalyzerResult. """ - def setUp(self): + def setUp(self) -> None: """ Set up the test. """ self.analyzer_result = analyzer_result.AnalyzerResult() self.result_dir = tempfile.mkdtemp() self.test_files = os.path.join(os.path.dirname(__file__), 'pvs_studio_output_test_files') - def tearDown(self): + def tearDown(self) -> None: """Clean temporary directory. """ shutil.rmtree(self.result_dir) - def test_no_report_output_file(self): + def test_no_report_output_file(self) -> None: """ Test transforming single cpp file. """ result = os.path.join(self.test_files, "files", "sample.cpp") is_success = self.analyzer_result.transform( - [result], self.result_dir, + [result], + self.result_dir, + plist.EXTENSION, file_name="{source_file}_{analyzer}" ) self.assertFalse(is_success) - def test_transform_dir(self): + def test_transform_dir(self) -> None: """ Test transforming a directory. """ result = os.path.join(self.test_files) is_success = self.analyzer_result.transform( [result], self.result_dir, + plist.EXTENSION, file_name="{source_file}_{analyzer}" ) self.assertFalse(is_success) - def test_transform_single_file(self): + def test_transform_single_file(self) -> None: """ Test transforming single output file. """ analyzer_result = os.path.join(self.test_files, 'sample.out') self.analyzer_result.transform( - [analyzer_result], self.result_dir, + [analyzer_result], + self.result_dir, + plist.EXTENSION, file_name="{source_file}_{analyzer}") plist_file = os.path.join(self.result_dir, From 2af178c6054b3ff1ed79be866b048d5e685c6054 Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Thu, 3 Oct 2024 10:47:23 +0300 Subject: [PATCH 06/11] Tests fix --- .../unit/analyzers/test_pvs_studio_parser.py | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py index 60f4a48265..6561e987f4 100644 --- a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py +++ b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py @@ -3,7 +3,7 @@ import shutil import plistlib import os - +import json from codechecker_report_converter.analyzers.pvs_studio import analyzer_result from codechecker_report_converter.report.parser import plist @@ -15,9 +15,9 @@ class PvsStudioAnalyzerResultTestCase(unittest.TestCase): def setUp(self) -> None: """ Set up the test. """ self.analyzer_result = analyzer_result.AnalyzerResult() - self.result_dir = tempfile.mkdtemp() self.test_files = os.path.join(os.path.dirname(__file__), 'pvs_studio_output_test_files') + self.result_dir = tempfile.mkdtemp() def tearDown(self) -> None: """Clean temporary directory. """ @@ -28,9 +28,9 @@ def test_no_report_output_file(self) -> None: result = os.path.join(self.test_files, "files", "sample.cpp") is_success = self.analyzer_result.transform( - [result], - self.result_dir, - plist.EXTENSION, + analyzer_result_file_paths=[result], + output_dir_path=self.result_dir, + export_type=plist.EXTENSION, file_name="{source_file}_{analyzer}" ) @@ -41,9 +41,9 @@ def test_transform_dir(self) -> None: result = os.path.join(self.test_files) is_success = self.analyzer_result.transform( - [result], - self.result_dir, - plist.EXTENSION, + analyzer_result_file_paths=[result], + output_dir_path=self.result_dir, + export_type=plist.EXTENSION, file_name="{source_file}_{analyzer}" ) @@ -51,15 +51,20 @@ def test_transform_dir(self) -> None: def test_transform_single_file(self) -> None: """ Test transforming single output file. """ - analyzer_result = os.path.join(self.test_files, 'sample.out') - self.analyzer_result.transform( - [analyzer_result], - self.result_dir, - plist.EXTENSION, - file_name="{source_file}_{analyzer}") + result = os.path.join(self.test_files, 'sample.json') + + self.make_report_valid() + is_success = self.analyzer_result.transform( + analyzer_result_file_paths=[result], + output_dir_path=self.result_dir, + export_type=plist.EXTENSION, + file_name="{source_file}_{analyzer}" + ) + + self.assertTrue(is_success) plist_file = os.path.join(self.result_dir, - 'sample.plist') + 'sample.cpp_pvs-studio.plist') with open(plist_file, mode='rb') as pfile: res = plistlib.load(pfile) @@ -74,3 +79,25 @@ def test_transform_single_file(self) -> None: exp = plistlib.load(pfile) self.assertEqual(res, exp) + + @staticmethod + def make_report_valid() -> None: + samples_path = os.path.join( + os.path.dirname(__file__), + "pvs_studio_output_test_files" + ) + report_path = os.path.join(samples_path, "sample.json") + with open(report_path, 'r') as file: + data = json.loads(file.read()) + data["warnings"][0]["positions"][0]["file"] = os.path.join( + samples_path, + "files", + "sample.cpp" + ) + + with open(report_path, "w") as file: + file.write(json.dumps(data)) + + +if __name__ == "__main__": + unittest.main() From 308b8740969f8d4217f3a4e60694bd4173b94734 Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Thu, 3 Oct 2024 14:53:27 +0300 Subject: [PATCH 07/11] More specific tests and documentation --- docs/supported_code_analyzers.md | 3 ++ docs/tools/report-converter.md | 16 +++++++ .../unit/analyzers/test_pvs_studio_parser.py | 44 ++++++++++++++----- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/docs/supported_code_analyzers.md b/docs/supported_code_analyzers.md index 3ce580e5c9..523096e6cb 100644 --- a/docs/supported_code_analyzers.md +++ b/docs/supported_code_analyzers.md @@ -23,10 +23,13 @@ CodeChecker result directory which can be stored to a CodeChecker server. | | [Sparse](/docs/tools/report-converter.md#sparse) | ✓ | | | [cpplint](/docs/tools/report-converter.md#cpplint) | ✓ | | | [GNU GCC Static Analyzer](/docs/tools/report-converter.md#gcc) | ✓ | +| | [PVS-Studio](/docs/tools/report-converter.md#PVS-Studio) | ✓ | | **C#** | [Roslynator.DotNet.Cli](/docs/tools/report-converter.md#roslynatordotnetcli) | ✓ | +| | [PVS-Studio](/docs/tools/report-converter.md#PVS-Studio) | ✓ | | **Java** | [FindBugs](http://findbugs.sourceforge.net/) | ✗ | | | [SpotBugs](/docs/tools/report-converter.md#spotbugs) | ✓ | | | [Facebook Infer](/docs/tools/report-converter.md#facebook-infer) | ✓ | +| | [PVS-Studio](/docs/tools/report-converter.md#PVS-Studio) | ✓ | | **Python** | [Pylint](/docs/tools/report-converter.md#pylint) | ✓ | | | [Pyflakes](/docs/tools/report-converter.md#pyflakes) | ✓ | | | [mypy](http://mypy-lang.org/) | ✗ | diff --git a/docs/tools/report-converter.md b/docs/tools/report-converter.md index 35eb9faf07..ee145245d5 100644 --- a/docs/tools/report-converter.md +++ b/docs/tools/report-converter.md @@ -22,6 +22,7 @@ a CodeChecker server. * [TSLint](#tslint) * [Golint](#golint) * [Pyflakes](#pyflakes) + * [PVS-Studio](#PVS-Studio) * [Markdownlint](#markdownlint) * [Coccinelle](#coccinelle) * [Smatch](#smatch) @@ -399,6 +400,21 @@ report-converter -t pyflakes -o ./codechecker_pyflakes_reports ./pyflakes_report CodeChecker store ./codechecker_pyflakes_reports -n pyflakes ``` +### [PVS-Studio](https://pvs-studio.com/en) +[PVS-Studio](https://pvs-studio.com/en) is a static analyzer on guard of code quality, +security (SAST), and code safety for C, C++, C# and Java. + +Detailed documentation on how to run the analysis can be found [on our website](https://pvs-studio.com/en/docs/). + +```sh +# Use 'report-converter' to create a CodeChecker report directory from the +# JSON report of PVS-Studio. +report-converter -t pvs_studio -o ./codechecker_pvs_studio_reports ./PVS-Studio.json + +# Store the PVS-Studio reports with CodeChecker. +CodeChecker store ./codechecker_pyflakes_reports -n pyflakes +``` + ### [TSLint](https://palantir.github.io/tslint) [TSLint](https://palantir.github.io/tslint) is a static analysis tool for `TypeScript`. diff --git a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py index 6561e987f4..62ceb22e7d 100644 --- a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py +++ b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py @@ -15,8 +15,10 @@ class PvsStudioAnalyzerResultTestCase(unittest.TestCase): def setUp(self) -> None: """ Set up the test. """ self.analyzer_result = analyzer_result.AnalyzerResult() - self.test_files = os.path.join(os.path.dirname(__file__), - 'pvs_studio_output_test_files') + self.test_files = os.path.join( + os.path.dirname(__file__), + 'pvs_studio_output_test_files' + ) self.result_dir = tempfile.mkdtemp() def tearDown(self) -> None: @@ -51,9 +53,9 @@ def test_transform_dir(self) -> None: def test_transform_single_file(self) -> None: """ Test transforming single output file. """ + self.make_report_valid() result = os.path.join(self.test_files, 'sample.json') - self.make_report_valid() is_success = self.analyzer_result.transform( analyzer_result_file_paths=[result], output_dir_path=self.result_dir, @@ -68,17 +70,22 @@ def test_transform_single_file(self) -> None: 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.cpp') plist_file = os.path.join(self.test_files, 'sample.plist') - with open(plist_file, mode='rb') as pfile: exp = plistlib.load(pfile) - self.assertEqual(res, exp) + self.assertEqual( + res["diagnostics"][0]["check_name"], + exp["diagnostics"][0]["check_name"] + ) + + self.assertEqual( + res["diagnostics"][0]["location"]["line"], + exp["diagnostics"][0]["location"]["line"] + ) @staticmethod def make_report_valid() -> None: @@ -86,18 +93,31 @@ def make_report_valid() -> None: os.path.dirname(__file__), "pvs_studio_output_test_files" ) + + path_to_file = os.path.join( + samples_path, + "files", + "sample.cpp" + ) + report_path = os.path.join(samples_path, "sample.json") with open(report_path, 'r') as file: data = json.loads(file.read()) - data["warnings"][0]["positions"][0]["file"] = os.path.join( - samples_path, - "files", - "sample.cpp" - ) + data["warnings"][0]["positions"][0]["file"] = path_to_file with open(report_path, "w") as file: file.write(json.dumps(data)) + path_to_plist = os.path.join(samples_path, "sample.plist") + + with open(path_to_plist, "rb") as plist_file: + data = plistlib.load(plist_file) + print(data) + data["files"][0] = path_to_file + + with open(path_to_plist, "wb") as plist_file: + plistlib.dump(data, plist_file) + if __name__ == "__main__": unittest.main() From 52a5d718843a18b2cc7c9947ea6647a07421a827 Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Wed, 9 Oct 2024 10:48:44 +0300 Subject: [PATCH 08/11] Small typo fix --- docs/tools/report-converter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tools/report-converter.md b/docs/tools/report-converter.md index ee145245d5..59d809d87f 100644 --- a/docs/tools/report-converter.md +++ b/docs/tools/report-converter.md @@ -409,7 +409,7 @@ Detailed documentation on how to run the analysis can be found [on our website]( ```sh # Use 'report-converter' to create a CodeChecker report directory from the # JSON report of PVS-Studio. -report-converter -t pvs_studio -o ./codechecker_pvs_studio_reports ./PVS-Studio.json +report-converter -t pvs-studio -o ./codechecker_pvs_studio_reports ./PVS-Studio.json # Store the PVS-Studio reports with CodeChecker. CodeChecker store ./codechecker_pyflakes_reports -n pyflakes From 88e657f3e8c81309fd14297d18546dea6d6493be Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Fri, 25 Oct 2024 09:28:06 +0300 Subject: [PATCH 09/11] Updates after @dkrupp review --- .../unit/analyzers/test_pvs_studio_parser.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py index 62ceb22e7d..8dadd24a56 100644 --- a/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py +++ b/tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py @@ -35,7 +35,7 @@ def test_no_report_output_file(self) -> None: export_type=plist.EXTENSION, file_name="{source_file}_{analyzer}" ) - + self.assertFalse(is_success) self.assertFalse(is_success) def test_transform_dir(self) -> None: @@ -70,7 +70,6 @@ def test_transform_single_file(self) -> None: with open(plist_file, mode='rb') as pfile: res = plistlib.load(pfile) - res['files'][0] = os.path.join('files', 'sample.cpp') plist_file = os.path.join(self.test_files, 'sample.plist') @@ -87,8 +86,16 @@ def test_transform_single_file(self) -> None: exp["diagnostics"][0]["location"]["line"] ) + self.assertEqual( + res["diagnostics"][0]["issue_hash_content_of_line_in_context"], + exp["diagnostics"][0]["issue_hash_content_of_line_in_context"] + ) + @staticmethod def make_report_valid() -> None: + """ The method sets absolute paths in PVS-Studio report + and .plist sample. """ + samples_path = os.path.join( os.path.dirname(__file__), "pvs_studio_output_test_files" @@ -101,18 +108,17 @@ def make_report_valid() -> None: ) report_path = os.path.join(samples_path, "sample.json") - with open(report_path, 'r') as file: + with open(report_path, 'r', encoding="utf-8") as file: data = json.loads(file.read()) data["warnings"][0]["positions"][0]["file"] = path_to_file - with open(report_path, "w") as file: + with open(report_path, "w", encoding="utf-8") as file: file.write(json.dumps(data)) path_to_plist = os.path.join(samples_path, "sample.plist") with open(path_to_plist, "rb") as plist_file: data = plistlib.load(plist_file) - print(data) data["files"][0] = path_to_file with open(path_to_plist, "wb") as plist_file: From 67394b596d1a3a00e60e6b0c727e38e80ea654fe Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Wed, 30 Oct 2024 16:35:07 +0300 Subject: [PATCH 10/11] Small fixes --- docs/tools/report-converter.md | 2 +- .../analyzers/pvs_studio/__init__.py | 7 +++++++ .../analyzers/pvs_studio/analyzer_result.py | 10 +++++----- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/tools/report-converter.md b/docs/tools/report-converter.md index 59d809d87f..a09c04299a 100644 --- a/docs/tools/report-converter.md +++ b/docs/tools/report-converter.md @@ -412,7 +412,7 @@ Detailed documentation on how to run the analysis can be found [on our website]( report-converter -t pvs-studio -o ./codechecker_pvs_studio_reports ./PVS-Studio.json # Store the PVS-Studio reports with CodeChecker. -CodeChecker store ./codechecker_pyflakes_reports -n pyflakes +CodeChecker store ./codechecker_pvs_studio_reports -n pvs_studio ``` ### [TSLint](https://palantir.github.io/tslint) diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py index e69de29bb2..4259749345 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__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/analyzers/pvs_studio/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py index 5276a89b0d..446367950c 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py @@ -36,7 +36,7 @@ def get_reports(self, file_path: str) -> List[Report]: reports: List[Report] = [] if not os.path.exists(file_path): - LOG.error("Report file does not exist: %s", file_path) + LOG.info("Report file does not exist: %s", file_path) return reports try: @@ -46,9 +46,9 @@ def get_reports(self, file_path: str) -> List[Report]: errors="ignore") as report_file: bugs = json.load(report_file)['warnings'] except (IOError, json.decoder.JSONDecodeError): - LOG.error("Failed to parse the given analyzer result '%s'. Please " - "give a valid json file generated by PVS-Studio.", - file_path) + LOG.warning("Failed to parse the given analyzer result '%s'. Please " + "give a valid json file generated by PVS-Studio.", + file_path) return reports file_cache: Dict[str, File] = {} @@ -57,7 +57,7 @@ def get_reports(self, file_path: str) -> List[Report]: for position in bug_positions: if not os.path.exists(position['file']): - LOG.error( + LOG.warning( "Source file does not exist: %s", position['file'] ) From 9061b098e59a93ce21af2cbca6ee553814e46bc1 Mon Sep 17 00:00:00 2001 From: Valerii Filatov Date: Wed, 30 Oct 2024 17:59:14 +0300 Subject: [PATCH 11/11] Linter E501 fix --- .../analyzers/pvs_studio/analyzer_result.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py index 446367950c..8a72986761 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py @@ -46,8 +46,9 @@ def get_reports(self, file_path: str) -> List[Report]: errors="ignore") as report_file: bugs = json.load(report_file)['warnings'] except (IOError, json.decoder.JSONDecodeError): - LOG.warning("Failed to parse the given analyzer result '%s'. Please " - "give a valid json file generated by PVS-Studio.", + LOG.warning("Failed to parse the given analyzer result '%s'. " + "Please give a valid json file " + "generated by PVS-Studio.", file_path) return reports