Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into branch-2-backup
Browse files Browse the repository at this point in the history
  • Loading branch information
feyruzb committed Jan 8, 2025
2 parents 6148c4d + 8421e16 commit 4f083b4
Show file tree
Hide file tree
Showing 62 changed files with 2,708 additions and 602 deletions.
29 changes: 23 additions & 6 deletions analyzer/codechecker_analyzer/analyzer_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from codechecker_analyzer.arg import analyzer_binary
from codechecker_common import logger
from codechecker_common.checker_labels import CheckerLabels
from codechecker_common.guidelines import Guidelines
from codechecker_common.singleton import Singleton
from codechecker_common.util import load_json
from pathlib import Path
Expand Down Expand Up @@ -52,13 +53,17 @@ def __init__(self):
if 'CC_TEST_LABELS_DIR' in os.environ:
labels_dir = os.environ['CC_TEST_LABELS_DIR']

guidelines_dir = os.path.join(self._data_files_dir_path,
'config', 'guidelines')

cfg_dict = self.__get_package_config()
self.env_vars = cfg_dict['environment_variables']

lcfg_dict = self.__get_package_layout()
self.pckg_layout = lcfg_dict['runtime']

self._checker_labels = CheckerLabels(labels_dir)
self._guidelines = Guidelines(guidelines_dir)
self.__package_version = None
self.__package_build_date = None
self.__package_git_hash = None
Expand All @@ -71,12 +76,20 @@ def __init__(self):
# Original caller environment of CodeChecker for external binaries
self.__original_env = None

self.logger_lib_dir_path = os.path.join(
self._data_files_dir_path, 'ld_logger', 'lib')

if not os.path.exists(self.logger_lib_dir_path):
self.logger_lib_dir_path = os.path.join(
self._lib_dir_path, 'codechecker_analyzer', 'ld_logger', 'lib')
# Find the path which has the architectures for the built ld_logger
# shared objects.
ld_logger_path = Path(self._data_files_dir_path, "ld_logger", "lib")
if not ld_logger_path.is_dir():
ld_logger_path = Path(
self._lib_dir_path, "codechecker_analyzer", "ld_logger", "lib"
)

# Add all children (architecture) paths to be later used in the
# LD_LIBRARY_PATH environment variable during logging of compiler
# invocations.
self.logger_lib_dir_path = ":".join(
[str(arch) for arch in ld_logger_path.iterdir() if arch.is_dir()]
)

self.logger_bin = None
self.logger_file = None
Expand Down Expand Up @@ -370,6 +383,10 @@ def checker_plugin(self):
def checker_labels(self):
return self._checker_labels

@property
def guideline(self):
return self._guidelines


def get_context():
try:
Expand Down
16 changes: 9 additions & 7 deletions analyzer/codechecker_analyzer/analyzers/analyzer_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def print_unsupported_analyzers(errored):
analyzer_binary, reason)


def check_available_analyzers(args_analyzers=None):
def check_available_analyzers(args_analyzers=None, exit_on_error=True):
"""
Handle use case when no analyzer can be found or a supported, explicitly
given analyzer cannot be found on the user machine.
Expand All @@ -135,17 +135,19 @@ def check_available_analyzers(args_analyzers=None):
analyzers, errored = check_supported_analyzers(args_analyzers)
if errored:
print_unsupported_analyzers(errored)
LOG.error("Failed to run command because the given analyzer(s) "
"cannot be found on your machine!")
sys.exit(1)
if exit_on_error:
LOG.error("Failed to run command because the given analyzer(s)"
" cannot be found on your machine!")
sys.exit(1)

else:
analyzers, errored = check_supported_analyzers(supported_analyzers)
if not analyzers:
print_unsupported_analyzers(errored)
LOG.error("Failed to run command because no analyzers can be "
"found on your machine!")
sys.exit(1)
if exit_on_error:
LOG.error("Failed to run command because no analyzers can be "
"found on your machine!")
sys.exit(1)
return analyzers, errored


Expand Down
3 changes: 3 additions & 0 deletions analyzer/codechecker_analyzer/analyzers/clangtidy/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ def get_analyzer_checkers(cls):
("clang-diagnostic-" + warning, "")
for warning in get_warnings())

checker_description.append(("clang-diagnostic-error",
"Indicates compiler errors."))

cls.__analyzer_checkers = checker_description

return checker_description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,3 @@ def add_checker(self, checker_name, description='',
return

super().add_checker(checker_name, description, state)

def set_checker_enabled(self, checker_name, enabled=True):
"""
Enable checker, keep description if already set.
"""
if checker_name.startswith('W') or \
checker_name.startswith('clang-diagnostic'):
self.add_checker(checker_name)

super().set_checker_enabled(checker_name, enabled)
124 changes: 79 additions & 45 deletions analyzer/codechecker_analyzer/analyzers/config_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from abc import ABCMeta
from enum import Enum
from string import Template
import collections
import platform
import sys
Expand Down Expand Up @@ -86,15 +85,16 @@ def add_checker(self, checker_name, description='',
"""
self.__available_checkers[checker_name] = (state, description)

def set_checker_enabled(self, checker_name, enabled=True):
def set_checker_enabled(self, checker_name, enabled=True, is_strict=False):
"""
Explicitly handle checker state, keep description if already set.
"""
changed_states = []
regex = "^" + re.escape(str(checker_name)) + "\\b.*$"

for ch_name, values in self.__available_checkers.items():
if re.match(regex, ch_name):
if (is_strict and ch_name == checker_name) \
or (not is_strict and re.match(regex, ch_name)):
_, description = values
state = CheckerState.ENABLED if enabled \
else CheckerState.DISABLED
Expand All @@ -118,7 +118,7 @@ def checks(self):
"""
return self.__available_checkers

def __gen_name_variations(self):
def __gen_name_variations(self, only_prefix=False):
"""
Generate all applicable name variations from the given checker list.
"""
Expand All @@ -134,9 +134,9 @@ def __gen_name_variations(self):
# ['misc', 'misc-dangling', 'misc-dangling-handle']
# from 'misc-dangling-handle'.
v = [delim.join(parts[:(i + 1)]) for i in range(len(parts))]
reserved_names += v
reserved_names += v[:-1] if only_prefix else v

return reserved_names
return list(set(reserved_names))

def initialize_checkers(self,
checkers,
Expand Down Expand Up @@ -184,7 +184,7 @@ def initialize_checkers(self,
else:
# Turn default checkers on.
for checker in default_profile_checkers:
self.set_checker_enabled(checker)
self.set_checker_enabled(checker, is_strict=True)

self.enable_all = enable_all
# If enable_all is given, almost all checkers should be enabled.
Expand All @@ -207,48 +207,82 @@ def initialize_checkers(self,
self.set_checker_enabled(checker_name)

# Set user defined enabled or disabled checkers from the command line.
for identifier, enabled in cmdline_enable:
labels = checker_labels.labels() \
if callable(getattr(checker_labels, 'labels', None)) \
else ["guideline", "profile", "severity", "sei-cert"]

# Construct a list of reserved checker names.
# (It is used to check if a profile name is valid.)
reserved_names = self.__gen_name_variations()
profiles = checker_labels.get_description('profile')
guidelines = checker_labels.occurring_values('guideline')
all_namespaces = ["checker", "prefix"] + labels

templ = Template("The ${entity} name '${identifier}' conflicts with a "
"checker name prefix '${identifier}'. Please use -e "
"${entity}:${identifier} to enable checkers of the "
"${identifier} ${entity} or use -e "
"prefix:${identifier} to select checkers which have "
"a name starting with '${identifier}'.")
all_options = dict(zip(labels, map(
checker_labels.occurring_values, labels)))

for identifier, enabled in cmdline_enable:
if "prefix:" in identifier:
identifier = identifier.replace("prefix:", "")
self.set_checker_enabled(identifier, enabled)

elif ':' in identifier:
for checker in checker_labels.checkers_by_labels([identifier]):
self.set_checker_enabled(checker, enabled)

elif identifier in profiles:
if identifier in reserved_names:
LOG.error(templ.substitute(entity="profile",
identifier=identifier))
all_options["prefix"] = list(set(self.__gen_name_variations(
only_prefix=True)))

all_options["checker"] = self.__available_checkers

if ":" in identifier:
identifier_namespace = identifier.split(":")[0]
identifier = identifier.split(":", 1)[1]

if identifier_namespace not in all_namespaces:
LOG.error("The %s namespace is not known. Please select"
"one of these existing namespace options: %s.",
identifier_namespace, ", ".join(all_namespaces))
sys.exit(1)
else:
for checker in checker_labels.checkers_by_labels(
[f'profile:{identifier}']):
self.set_checker_enabled(checker, enabled)

elif identifier in guidelines:
if identifier in reserved_names:
LOG.error(templ.substitute(entity="guideline",
identifier=identifier))

# TODO: Each analyzer has its own config handler and is unaware
# of other checkers. To avoid not reliable error, we pass
# checker:'any_options' and prefix:'any_options'. This ensures
# enabling a checker doesn't inadvertently cause an error in a
# different analyzer. Ideally, there should be a centralized
# main configuration accessible to all analyzers.
if identifier not in all_options[identifier_namespace] \
and identifier_namespace not in ("checker", "prefix"):
LOG.error("The %s identifier does not exist in the %s "
"namespace. Please select one of these "
"existing options: %s.", identifier,
identifier_namespace, ", ".join(
all_options[identifier_namespace]))
sys.exit(1)
else:
for checker in checker_labels.checkers_by_labels(
[f'guideline:{identifier}']):
self.set_checker_enabled(checker, enabled)

self.initialize_checkers_by_namespace(
identifier_namespace, identifier, enabled)

else:
self.set_checker_enabled(identifier, enabled)
possible_options = {}
for label, options in all_options.items():
if identifier in options:
possible_options[label] = identifier

if len(possible_options) == 1:
self.initialize_checkers_by_namespace(
*list(possible_options.items())[0], enabled)
elif len(possible_options) > 1:
error_options = ", ".join(f"{label}:{option}"
for label, option
in possible_options.items())

LOG.error("The %s is ambigous. Please select one of these"
" options to clarify the checker list: %s.",
identifier, error_options)
sys.exit(1)
else:
# The identifier is not known but we just pass it
# and handle it in a different section.
continue

def initialize_checkers_by_namespace(self,
identifier_namespace,
identifier,
enabled):
if identifier_namespace == "checker":
self.set_checker_enabled(identifier, enabled, is_strict=True)
elif identifier_namespace == "prefix":
self.set_checker_enabled(identifier, enabled)
else:
checker_labels = analyzer_context.get_context().checker_labels
for checker in checker_labels.checkers_by_labels(
[f"{identifier_namespace}:{identifier}"]):
self.set_checker_enabled(checker, enabled, is_strict=True)
7 changes: 1 addition & 6 deletions analyzer/codechecker_analyzer/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ def available(ordered_checkers, available_checkers):
"""
missing_checkers = set()
for checker_name, _ in ordered_checkers:
# TODO: This label list shouldn't be hard-coded here.
if checker_name.startswith('profile:') or \
checker_name.startswith('guideline:') or \
checker_name.startswith('severity:') or \
checker_name.startswith('sei-cert:') or \
checker_name.startswith('prefix:'):
if ":" in checker_name:
continue

name_match = False
Expand Down
14 changes: 10 additions & 4 deletions analyzer/codechecker_analyzer/cmd/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,8 +773,11 @@ def add_arguments_to_parser(parser):
"between the checker prefix "
"group/profile/guideline name, the use of "
"one of the following labels is "
"mandatory: 'prefix:', 'profile:', "
"'guideline:'.")
"mandatory: 'checker:', 'prefix:', "
"'profile:', 'guideline:'. If a checker "
"name matches multiple checkers as a "
"prefix, 'checker:' or 'prefix:' "
"namespace is required")

checkers_opts.add_argument('-d', '--disable',
dest="disable",
Expand All @@ -792,8 +795,11 @@ def add_arguments_to_parser(parser):
"between the checker prefix "
"group/profile/guideline name, the use of "
"one of the following labels is "
"mandatory: 'prefix:', 'profile:', "
"'guideline:'.")
"mandatory: 'checker:', 'prefix:', "
"'profile:', 'guideline:'. If a checker "
"name matches multiple checkers as a "
"prefix, 'checker:' or 'prefix:' "
"namespace is required")

checkers_opts.add_argument('--enable-all',
dest="enable_all",
Expand Down
Loading

0 comments on commit 4f083b4

Please sign in to comment.