-
Notifications
You must be signed in to change notification settings - Fork 379
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4356 from feeelin/master
PVS-Studio Static Code Analyzer support
- Loading branch information
Showing
8 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
# | ||
# ------------------------------------------------------------------------- |
82 changes: 82 additions & 0 deletions
82
tools/report-converter/codechecker_report_converter/analyzers/pvs_studio/analyzer_result.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# ------------------------------------------------------------------------- | ||
# | ||
# 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/' | ||
|
||
__severities = ["UNSPECIFIED", "HIGH", "MEDIUM", "LOW"] | ||
|
||
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.info("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.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] = {} | ||
for bug in bugs: | ||
bug_positions = bug['positions'] | ||
|
||
for position in bug_positions: | ||
if not os.path.exists(position['file']): | ||
LOG.warning( | ||
"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'], | ||
severity=self.get_diagnostic_severity(bug.get('level')) | ||
)) | ||
|
||
return reports | ||
|
||
def get_diagnostic_severity(self, level: int) -> str: | ||
return self.__severities[level] |
13 changes: 13 additions & 0 deletions
13
tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/files/sample.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#include "stdafx.h" | ||
|
||
int main() | ||
{ | ||
int a = 5; | ||
int b = 6; | ||
|
||
if (a < b) { | ||
return 1; | ||
} | ||
|
||
return 0; | ||
} |
27 changes: 27 additions & 0 deletions
27
tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
] | ||
} |
69 changes: 69 additions & 0 deletions
69
tools/report-converter/tests/unit/analyzers/pvs_studio_output_test_files/sample.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>diagnostics</key> | ||
<array> | ||
<dict> | ||
<key>category</key> | ||
<string>unknown</string> | ||
<key>check_name</key> | ||
<string>V547</string> | ||
<key>description</key> | ||
<string>Expression 'a < b' is always true.</string> | ||
<key>issue_hash_content_of_line_in_context</key> | ||
<string>ba476be198a6e52c12427e1d8606baa8</string> | ||
<key>location</key> | ||
<dict> | ||
<key>col</key> | ||
<integer>0</integer> | ||
<key>file</key> | ||
<integer>0</integer> | ||
<key>line</key> | ||
<integer>8</integer> | ||
</dict> | ||
<key>path</key> | ||
<array> | ||
<dict> | ||
<key>depth</key> | ||
<integer>0</integer> | ||
<key>kind</key> | ||
<string>event</string> | ||
<key>location</key> | ||
<dict> | ||
<key>col</key> | ||
<integer>0</integer> | ||
<key>file</key> | ||
<integer>0</integer> | ||
<key>line</key> | ||
<integer>8</integer> | ||
</dict> | ||
<key>message</key> | ||
<string>Expression 'a < b' is always true.</string> | ||
</dict> | ||
</array> | ||
<key>type</key> | ||
<string>pvs-studio</string> | ||
</dict> | ||
</array> | ||
<key>files</key> | ||
<array> | ||
<string>files\sample.cpp</string> | ||
</array> | ||
<key>metadata</key> | ||
<dict> | ||
<key>analyzer</key> | ||
<dict> | ||
<key>name</key> | ||
<string>pvs-studio</string> | ||
</dict> | ||
<key>generated_by</key> | ||
<dict> | ||
<key>name</key> | ||
<string>report-converter</string> | ||
<key>version</key> | ||
<string>0.1.0</string> | ||
</dict> | ||
</dict> | ||
</dict> | ||
</plist> |
129 changes: 129 additions & 0 deletions
129
tools/report-converter/tests/unit/analyzers/test_pvs_studio_parser.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import unittest | ||
import tempfile | ||
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 | ||
|
||
|
||
class PvsStudioAnalyzerResultTestCase(unittest.TestCase): | ||
""" Test the output of PVS-Studio's AnalyzerResult. """ | ||
|
||
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.result_dir = tempfile.mkdtemp() | ||
|
||
def tearDown(self) -> None: | ||
"""Clean temporary directory. """ | ||
shutil.rmtree(self.result_dir) | ||
|
||
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( | ||
analyzer_result_file_paths=[result], | ||
output_dir_path=self.result_dir, | ||
export_type=plist.EXTENSION, | ||
file_name="{source_file}_{analyzer}" | ||
) | ||
self.assertFalse(is_success) | ||
self.assertFalse(is_success) | ||
|
||
def test_transform_dir(self) -> None: | ||
""" Test transforming a directory. """ | ||
result = os.path.join(self.test_files) | ||
|
||
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.assertFalse(is_success) | ||
|
||
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') | ||
|
||
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.cpp_pvs-studio.plist') | ||
|
||
with open(plist_file, mode='rb') as pfile: | ||
res = plistlib.load(pfile) | ||
|
||
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["diagnostics"][0]["check_name"], | ||
exp["diagnostics"][0]["check_name"] | ||
) | ||
|
||
self.assertEqual( | ||
res["diagnostics"][0]["location"]["line"], | ||
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" | ||
) | ||
|
||
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', encoding="utf-8") as file: | ||
data = json.loads(file.read()) | ||
data["warnings"][0]["positions"][0]["file"] = path_to_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) | ||
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() |