Skip to content

Commit

Permalink
[report-converter] Support sarif format and gcc analyzer
Browse files Browse the repository at this point in the history
Fixes #1797. Based on a commit authored by @csordasmarton. Credit goes
to him!

We've long wanted to support sarif
(https://sarifweb.azurewebsites.net/), and finally, this is the first
real step towards it!

This patch can both parse and export to sarif.

My intent is that the code is self explanatory (because I explained
things in the code!), there are two things I'd like to highlight:

1. I strugged a LOT with mypy, which lead me to express a things things
   in a rather cumbersome manner. I left comments around these parts
2. I copied all example tests from https://github.com/microsoft/sarif-tutorials/
   to tools/report-converter/tests/unit/parser/sarif/sarif_test_files/.
   These examples come with an MIT licence, which I also copied over.
  • Loading branch information
csordasmarton authored and Szelethus committed Oct 4, 2023
1 parent c3dbb1b commit 61cc0ce
Show file tree
Hide file tree
Showing 59 changed files with 3,555 additions and 61 deletions.
1 change: 1 addition & 0 deletions analyzer/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ lxml==4.9.2
portalocker==2.2.1
psutil==5.8.0
PyYAML==6.0.1
sarif-tools==1.0.0
mypy_extensions==0.4.3
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,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
#
# -------------------------------------------------------------------------

import logging
from typing import Dict, List

from codechecker_report_converter.report import File, Report
from codechecker_report_converter.report.parser import sarif

from ..analyzer_result import AnalyzerResultBase


LOG = logging.getLogger('report-converter')


class AnalyzerResult(AnalyzerResultBase):
""" Transform analyzer result of the GCC Static Analyzer. """

TOOL_NAME = 'gcc'
NAME = 'GNU Compiler Collection Static Analyzer'
URL = 'https://gcc.gnu.org/wiki/StaticAnalyzer'

def __init__(self):
super(AnalyzerResult, self).__init__()
self.__infer_out_parent_dir = None
self._file_cache: Dict[str, File] = {}

def get_reports(self, result_file_path: str) -> List[Report]:
""" Get reports from the given analyzer result file. """

return sarif.Parser().get_reports(result_file_path)
9 changes: 6 additions & 3 deletions tools/report-converter/codechecker_report_converter/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class RawDescriptionDefaultHelpFormatter(
analyzer_result = getattr(module, "AnalyzerResult")
supported_converters[analyzer_result.TOOL_NAME] = analyzer_result
except ModuleNotFoundError:
pass
raise


supported_metadata_keys = ["analyzer_command", "analyzer_version"]
Expand Down Expand Up @@ -188,6 +188,9 @@ def __add_arguments_to_parser(parser):
"Currently supported output types are: " +
', '.join(sorted(supported_converters)) + ".")

extensions_with_dot = \
sorted([f".{ext}" for ext in SUPPORTED_ANALYZER_EXTENSIONS])

parser.add_argument('-e', '--export',
type=str,
dest='export',
Expand All @@ -196,8 +199,8 @@ def __add_arguments_to_parser(parser):
default=plist.EXTENSION,
help="Specify the export format of the converted "
"reports. Currently supported export types "
"are: " + ', '.join(sorted(
SUPPORTED_ANALYZER_EXTENSIONS)) + ".")
"are: " +
', '.join(extensions_with_dot) + ".")

parser.add_argument('--meta',
nargs='*',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
Base parser class to parse analyzer result files.
"""

import json
import logging
import os

from abc import ABCMeta, abstractmethod
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple

from codechecker_report_converter import __title__, __version__
from codechecker_report_converter.report import File, Report
from codechecker_report_converter.report.checker_labels import CheckerLabels
from codechecker_report_converter.report.hash import HashType
Expand All @@ -22,6 +25,53 @@
LOG = logging.getLogger('report-converter')


def load_json(path: str):
"""
Load the contents of the given file as a JSON and return it's value,
or default if the file can't be loaded.
"""

ret = None
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as handle:
ret = json.load(handle)
except IOError as ex:
LOG.warning("Failed to open json file: %s", path)
LOG.warning(ex)
except OSError as ex:
LOG.warning("Failed to open json file: %s", path)
LOG.warning(ex)
except ValueError as ex:
LOG.warning("%s is not a valid json file.", path)
LOG.warning(ex)
except TypeError as ex:
LOG.warning('Failed to process json file: %s', path)
LOG.warning(ex)

return ret


def get_tool_info() -> Tuple[str, str]:
""" Get tool info.
If this was called through CodeChecker, this function will return
CodeChecker information, otherwise this tool (report-converter)
information.
"""
data_files_dir_path = os.environ.get('CC_DATA_FILES_DIR')
if data_files_dir_path:
analyzer_version_file_path = os.path.join(
data_files_dir_path, 'config', 'analyzer_version.json')
if os.path.exists(analyzer_version_file_path):
data = load_json(analyzer_version_file_path)
version = data.get('version')
if version:
return 'CodeChecker', f"{version['major']}." \
f"{version['minor']}.{version['revision']}"

return __title__, __version__


class AnalyzerInfo:
""" Hold information about the analyzer. """
def __init__(self, name: str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"""

import importlib
import json
import logging
import os
import plistlib
Expand All @@ -27,12 +26,11 @@
else:
from mypy_extensions import TypedDict

from codechecker_report_converter import __title__, __version__
from codechecker_report_converter.report import BugPathEvent, \
BugPathPosition, File, get_or_create_file, MacroExpansion, Range, Report
from codechecker_report_converter.report.hash import get_report_hash, HashType
from codechecker_report_converter.report.parser.base import AnalyzerInfo, \
BaseParser
BaseParser, get_tool_info


LOG = logging.getLogger('report-converter')
Expand Down Expand Up @@ -422,58 +420,13 @@ def __get_macro_expansions(

return macro_expansions

def __load_json(self, path: str):
"""
Load the contents of the given file as a JSON and return it's value,
or default if the file can't be loaded.
"""

ret = {}
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as handle:
ret = json.load(handle)
except IOError as ex:
LOG.warning("Failed to open json file: %s", path)
LOG.warning(ex)
except OSError as ex:
LOG.warning("Failed to open json file: %s", path)
LOG.warning(ex)
except ValueError as ex:
LOG.warning("%s is not a valid json file.", path)
LOG.warning(ex)
except TypeError as ex:
LOG.warning('Failed to process json file: %s', path)
LOG.warning(ex)

return ret

def __get_tool_info(self) -> Tuple[str, str]:
""" Get tool info.
If this was called through CodeChecker, this function will return
CodeChecker information, otherwise this tool (report-converter)
information.
"""
data_files_dir_path = os.environ.get('CC_DATA_FILES_DIR')
if data_files_dir_path:
analyzer_version_file_path = os.path.join(
data_files_dir_path, 'config', 'analyzer_version.json')
if os.path.exists(analyzer_version_file_path):
data = self.__load_json(analyzer_version_file_path)
version = data.get('version')
if version:
return 'CodeChecker', f"{version['major']}." \
f"{version['minor']}.{version['revision']}"

return __title__, __version__

def convert(
self,
reports: List[Report],
analyzer_info: Optional[AnalyzerInfo] = None
):
""" Converts the given reports. """
tool_name, tool_version = self.__get_tool_info()
tool_name, tool_version = get_tool_info()

data: Dict[str, Any] = {
'files': [],
Expand Down
Loading

0 comments on commit 61cc0ce

Please sign in to comment.