Skip to content

Commit

Permalink
Configurability of analyzer versions
Browse files Browse the repository at this point in the history
The modification can be used to validate the version of each analyzer.
The user can specify all desired analyzers after the --analyzers flag, as was possible before. Also, the exact version number can be typed for each analyzer, separated by an '==' char. For example: 'CodeChecker analyze compile_commands.json -o reports --analyzers clangsa==14.0.0 cppcheck==2.7'.
If the version number was not the same as in the current environment, the analyzer would be disabled. The user can enumerate the analyzers with or without versions, for example: 'CodeChecker analyze compile_commands.json -o reports --analyzers clangsa==14.0.0 cppcheck'.
  • Loading branch information
cservakt committed Aug 30, 2023
1 parent 38f9315 commit 4315b5a
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 47 deletions.
6 changes: 1 addition & 5 deletions analyzer/codechecker_analyzer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool,
in the given analysis context for the supplied build actions.
Additionally, insert statistical information into the metadata dict.
"""

context = analyzer_context.get_context()

ctu_reanalyze_on_failure = 'ctu_reanalyze_on_failure' in args and \
Expand All @@ -141,7 +140,6 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool,
else analyzer_types.supported_analyzers
analyzers, errored = analyzer_types.check_supported_analyzers(analyzers)
analyzer_types.check_available_analyzers(analyzers, errored)

ctu_collect = False
ctu_analyze = False
ctu_dir = ''
Expand Down Expand Up @@ -229,9 +227,7 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool,
if state == CheckerState.enabled:
enabled_checkers[analyzer].append(check)

# TODO: cppcheck may require a different environment than clang.
version = analyzer_types.supported_analyzers[analyzer] \
.get_version(context.analyzer_env)
version = analyzer_types.supported_analyzers[analyzer].get_version()
metadata_info['analyzer_statistics']['version'] = version

metadata_tool['analyzers'][analyzer] = metadata_info
Expand Down
27 changes: 27 additions & 0 deletions analyzer/codechecker_analyzer/analyzers/analyzer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ def config_handler(self):
def construct_analyzer_cmd(self, result_handler):
raise NotImplementedError("Subclasses should implement this!")

@classmethod
@abstractmethod
def analyzer_binary(cls):
"""
A subclass should have a analyzer_binary method
to return the bin of analyzer.
"""
pass

@classmethod
@abstractmethod
def analyzer_env(cls):
"""
A subclass should have a analyzer_env method
to return the env for analyzer.
"""
pass

@classmethod
def resolve_missing_binary(cls, configured_binary, environ):
"""
Expand All @@ -63,6 +81,15 @@ def version_compatible(cls, configured_binary, environ):
"""
raise NotImplementedError("Subclasses should implement this!")

@classmethod
@abstractmethod
def version_info(cls):
"""
A subclass should have a version_info method
to return with the analyzer's version number.
"""
pass

@classmethod
def construct_config_handler(cls, args):
""" Should return a subclass of AnalyzerConfigHandler."""
Expand Down
25 changes: 21 additions & 4 deletions analyzer/codechecker_analyzer/analyzers/analyzer_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from .clangsa.analyzer import ClangSA
from .cppcheck.analyzer import Cppcheck

from distutils.version import StrictVersion

LOG = get_logger('analyzer')

supported_analyzers = {ClangSA.ANALYZER_NAME: ClangSA,
Expand Down Expand Up @@ -113,8 +115,9 @@ def print_unsupported_analyzers(errored):
for analyzer_binary, reason in errored:
LOG.warning("Analyzer '%s' is enabled but CodeChecker is failed to "
"execute analysis with it: '%s'. Please check your "
"'PATH' environment variable and the "
"'config/package_layout.json' file!",
"'PATH' environment variable, the "
"'config/package_layout.json' file "
"and the --analyzers flag!",
analyzer_binary, reason)


Expand Down Expand Up @@ -148,8 +151,11 @@ def check_supported_analyzers(analyzers):

enabled_analyzers = set()
failed_analyzers = set()

for analyzer_name in analyzers:
analyzer_name, requested_version = analyzer_name.split('==', 1) \
if len(analyzer_name.split('==', 1)) == 2 \
else [analyzer_name, None]

if analyzer_name not in supported_analyzers:
failed_analyzers.add((analyzer_name,
"Analyzer unsupported by CodeChecker!"))
Expand Down Expand Up @@ -184,7 +190,18 @@ def check_supported_analyzers(analyzers):
# Check version compatibility of the analyzer binary.
if analyzer_bin:
analyzer = supported_analyzers[analyzer_name]
if not analyzer.version_compatible(analyzer_bin, check_env):
if requested_version:
bin_version = StrictVersion(str(analyzer.version_info()))
requested_version = StrictVersion(requested_version)
if requested_version != bin_version:
LOG.warning(
f"Given version: {requested_version}, found version "
f"for {analyzer_name} analyzer: {bin_version}"
)
failed_analyzers.add((analyzer_name,
"Wrong version given."))
available_analyzer = False
if not analyzer.version_compatible():
failed_analyzers.add((analyzer_name,
"Incompatible version."))
available_analyzer = False
Expand Down
13 changes: 10 additions & 3 deletions analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ def analyzer_binary(cls):
return analyzer_context.get_context() \
.analyzer_binaries[cls.ANALYZER_NAME]

@classmethod
def analyzer_env(cls):
"""
Return the env for analyzer.
"""
return analyzer_context.get_context().analyzer_env

@classmethod
def analyzer_plugins(cls) -> List[str]:
"""
Expand Down Expand Up @@ -152,12 +159,12 @@ def __add_plugin_load_flags(cls, analyzer_cmd: List[str]):
analyzer_cmd.extend(["-load", plugin])

@classmethod
def get_version(cls, env=None):
def get_version(cls):
""" Get analyzer version information. """
version = [cls.analyzer_binary(), '--version']
try:
output = subprocess.check_output(version,
env=env,
env=cls.analyzer_env(),
universal_newlines=True,
encoding="utf-8",
errors="ignore")
Expand Down Expand Up @@ -526,7 +533,7 @@ def resolve_missing_binary(cls, configured_binary, environ):
return clang

@classmethod
def version_compatible(cls, configured_binary, environ):
def version_compatible(cls):
"""
Check the version compatibility of the given analyzer binary.
"""
Expand Down
5 changes: 5 additions & 0 deletions analyzer/codechecker_analyzer/analyzers/clangsa/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ def __init__(self,
self.installed_dir = str(installed_dir)
self.vendor = str(vendor)

def __str__(self):
return f"{self.major_version}." \
f"{self.minor_version}." \
f"{self.patch_version}"


class ClangVersionInfoParser:
"""
Expand Down
27 changes: 24 additions & 3 deletions analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import subprocess
from typing import List, Tuple

from distutils.version import StrictVersion

import yaml

from codechecker_common.logger import get_logger
Expand Down Expand Up @@ -178,12 +180,16 @@ def analyzer_binary(cls):
.analyzer_binaries[cls.ANALYZER_NAME]

@classmethod
def get_version(cls, env=None):
def analyzer_env(cls):
return analyzer_context.get_context().analyzer_env

@classmethod
def get_version(cls):
""" Get analyzer version information. """
version = [cls.analyzer_binary(), '--version']
try:
output = subprocess.check_output(version,
env=env,
env=cls.analyzer_env(),
universal_newlines=True,
encoding="utf-8",
errors="ignore")
Expand All @@ -195,6 +201,21 @@ def get_version(cls, env=None):

return None

@classmethod
def version_info(cls):
"""
Run the Clang-tidy version command
parse the output
and return the analyzer's version.
"""
version_output = cls.get_version()
version_re = re.compile(r'version\s+(?P<version>[\d\.]+)')
match = version_re.search(version_output)
if match:
return StrictVersion(match.group('version'))
else:
return None

def add_checker_config(self, checker_cfg):
LOG.error("Not implemented yet")

Expand Down Expand Up @@ -466,7 +487,7 @@ def resolve_missing_binary(cls, configured_binary, environ):
return clangtidy

@classmethod
def version_compatible(cls, configured_binary, environ):
def version_compatible(cls):
"""
Check the version compatibility of the given analyzer binary.
"""
Expand Down
47 changes: 19 additions & 28 deletions analyzer/codechecker_analyzer/analyzers/cppcheck/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,6 @@ def parse_checkers(cppcheck_output):
return checkers


def parse_version(cppcheck_output):
"""
Parse cppcheck version output and return the version number.
"""
version_re = re.compile(r'^Cppcheck (?P<version>[\d\.]+)')
match = version_re.match(cppcheck_output)
if match:
return StrictVersion(match.group('version'))


class Cppcheck(analyzer_base.SourceAnalyzer):
"""
Constructs the Cppcheck analyzer commands.
Expand All @@ -82,12 +72,16 @@ def analyzer_binary(cls):
.analyzer_binaries[cls.ANALYZER_NAME]

@classmethod
def get_version(cls, env=None):
def analyzer_env(cls):
return os.environ

@classmethod
def get_version(cls):
""" Get analyzer version information. """
version = [cls.analyzer_binary(), '--version']
try:
output = subprocess.check_output(version,
env=env,
env=cls.analyzer_env(),
universal_newlines=True,
encoding="utf-8",
errors="ignore")
Expand Down Expand Up @@ -331,29 +325,26 @@ def resolve_missing_binary(cls, configured_binary, env):
return cppcheck

@classmethod
def __get_analyzer_version(cls, analyzer_binary, env):
def version_info(cls):
"""
Return the analyzer version.
Run the Cppcheck version command
parse the output
and return the analyzer's version.
"""
command = [analyzer_binary, "--version"]

try:
result = subprocess.check_output(
command,
env=env,
encoding="utf-8",
errors="ignore")
return parse_version(result)
except (subprocess.CalledProcessError, OSError):
return []
version_output = cls.get_version()
version_re = re.compile(r'^Cppcheck (?P<version>[\d\.]+)')
match = version_re.match(version_output)
if match:
return StrictVersion(match.group('version'))
else:
return None

@classmethod
def version_compatible(cls, configured_binary, environ):
def version_compatible(cls):
"""
Check the version compatibility of the given analyzer binary.
"""
analyzer_version = \
cls.__get_analyzer_version(configured_binary, environ)
analyzer_version = cls.version_info()

# The analyzer version should be above 1.80 because '--plist-output'
# argument was introduced in this release.
Expand Down
1 change: 0 additions & 1 deletion analyzer/codechecker_analyzer/cmd/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,6 @@ def add_arguments_to_parser(parser):
dest='analyzers',
metavar='ANALYZER',
required=False,
choices=analyzer_types.supported_analyzers,
default=argparse.SUPPRESS,
help="Run analysis only with the analyzers "
"specified. Currently supported analyzers "
Expand Down
1 change: 0 additions & 1 deletion analyzer/codechecker_analyzer/cmd/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ def add_arguments_to_parser(parser):
dest='analyzers',
metavar='ANALYZER',
required=False,
choices=analyzer_types.supported_analyzers,
default=argparse.SUPPRESS,
help="Run analysis only with the analyzers "
"specified. Currently supported analyzers "
Expand Down
6 changes: 4 additions & 2 deletions docs/analyzer/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ analyzer arguments:
--analyzers ANALYZER [ANALYZER ...]
Run analysis only with the analyzers specified.
Currently supported analyzers are: clangsa, clang-
tidy.
tidy, cppcheck. Version number can be also specified
for analyzers to verify them.
--capture-analysis-output
Store standard output and standard error of successful
analyzer invocations into the '<OUTPUT_DIR>/success'
Expand Down Expand Up @@ -1098,7 +1099,8 @@ analyzer arguments:
--analyzers ANALYZER [ANALYZER ...]
Run analysis only with the analyzers specified.
Currently supported analyzers are: clangsa, clang-
tidy.
tidy, cppcheck. Version number can be also specified
for analyzers to verify them.
--capture-analysis-output
Store standard output and standard error of successful
analyzer invocations into the '<OUTPUT_DIR>/success'
Expand Down

0 comments on commit 4315b5a

Please sign in to comment.