-
Notifications
You must be signed in to change notification settings - Fork 381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PVS-Studio Static Code Analyzer support #4356
Changes from all commits
7c0238c
9f8ad8b
fcb4f41
7c4c165
56202e8
2af178c
308b874
52a5d71
88e657f
67394b5
9061b09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
# | ||
# ------------------------------------------------------------------------- |
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] |
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; | ||
} |
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 | ||
} | ||
] | ||
} |
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> |
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a docstring comment to this function explaining what it does. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All right, I added this. |
||
""" 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does PVS studio support relative path to the source files? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, PVS-Studio uses absolute paths to source files. |
||
|
||
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you please add an assertion about the generated HASH? That would prove that the transform() generates the correct hash for the report.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All right, I added this.