From 895a846b98d6df42121b8ba1f99dfc92cc20a0d6 Mon Sep 17 00:00:00 2001 From: Nora Zinaeddin Date: Thu, 28 Mar 2024 14:41:08 +0100 Subject: [PATCH] Add functionality to validate analyzer and checker options When specifying invalid checker and analyzer config, the CodeChecker analyze command used to give a warning but continued running. This functionality throws an error and prevents the analysis from running in this case. --- analyzer/codechecker_analyzer/cmd/analyze.py | 88 +++++++++++++++++++- analyzer/tests/unit/test_checker_handling.py | 17 ++++ 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/analyzer/codechecker_analyzer/cmd/analyze.py b/analyzer/codechecker_analyzer/cmd/analyze.py index b676fbe9dc..1bfc2f2bc5 100644 --- a/analyzer/codechecker_analyzer/cmd/analyze.py +++ b/analyzer/codechecker_analyzer/cmd/analyze.py @@ -9,7 +9,6 @@ Execute analysis over an already existing build.json compilation database. """ - import argparse import collections import json @@ -35,7 +34,6 @@ SkipListHandlers from codechecker_common.util import load_json - LOG = logger.get_logger('system') header_file_extensions = ( @@ -797,6 +795,81 @@ def add_arguments_to_parser(parser): func=main, func_process_config_file=cmd_config.process_config_file) +def validate_analyzer_parameter(analyzer_conf): + """ + Ensure that the analyzer_config parameter is set to a valid value + by verifying if it belongs to the set of allowed values. + """ + analyzer = analyzer_conf.__getitem__(0).analyzer + + if analyzer in analyzer_types.supported_analyzers: + analyzer_class = analyzer_types.supported_analyzers[analyzer] + else: + LOG.error(f"Invalid --analyzer-config parameter: {analyzer} is not " + f"a supported analyzer. The supported analyzers are " + f"{', '.join(a for a in analyzer_types.supported_analyzers)}" + f".") + return False + + if not isinstance(analyzer_conf, list): + return False + + analyzers = [analyzer[0] for analyzer in + analyzer_class.get_analyzer_config()] + + # Make sure "[clang-tidy] Allow to override checker list #3203" works + if analyzer_class.ANALYZER_NAME == 'clang-tidy': + analyzers.append('take-config-from-directory') + + for cfg in analyzer_conf: + if cfg.analyzer == analyzer_class.ANALYZER_NAME and \ + cfg.option not in analyzers: + LOG.error(f"Invalid --analyzer-config parameter: " + f"{analyzer_class.ANALYZER_NAME} has no config named " + f"{cfg.option}. Use the 'CodeChecker analyzers " + f"--analyzer-config {analyzer_class.ANALYZER_NAME}' " + f"command to check available configs.") + return False + else: + return True + + +def validate_checker_parameter(checker_conf): + """ + Ensure that the checker_config parameter is set to a valid value + by verifying if it belongs to the set of allowed values. + """ + analyzer = checker_conf.__getitem__(0).analyzer + + if analyzer in analyzer_types.supported_analyzers: + analyzer_class = analyzer_types.supported_analyzers[analyzer] + else: + LOG.error(f"Invalid --checker-config parameter: {analyzer} is not " + f"a supported analyzer. The supported analyzers are " + f"{', '.join(a for a in analyzer_types.supported_analyzers)}" + f".") + return False + + if not isinstance(checker_conf, list): + return False + + checkers = [checker[0] for checker in + analyzer_class.get_analyzer_checkers()] + + for cfg in checker_conf: + if cfg.analyzer == analyzer_class.ANALYZER_NAME and \ + cfg.checker not in checkers: + LOG.error( + f"Invalid --checker-config parameter: " + f"{analyzer_class.ANALYZER_NAME} has no checker named " + f"{cfg.checker}. Use the 'CodeChecker checkers " + f"--checker-config' command to see available checkers." + ) + return False + else: + return True + + def get_affected_file_paths( file_filters: List[str], compile_commands: tu_collector.CompilationDB @@ -810,7 +883,7 @@ def get_affected_file_paths( file_paths.append(file_filter) if os.path.exists(file_filter) and \ - file_filter.endswith(header_file_extensions): + file_filter.endswith(header_file_extensions): LOG.info("Get dependent source files for '%s'...", file_filter) dependent_sources = tu_collector.get_dependent_sources( compile_commands, file_filter) @@ -934,6 +1007,15 @@ def main(args): """ logger.setup_logger(args.verbose if 'verbose' in args else None) + # Validate analyzer and checker config (if any) + config_validator = { + 'analyzer_config': validate_analyzer_parameter, + 'checker_config': validate_checker_parameter + } + for conf, validate_func in config_validator.items(): + if conf in args and not validate_func(getattr(args, conf)): + sys.exit(1) + if 'tidy_config' in args: LOG.warning( "--tidy-config is deprecated and will be removed in the next " diff --git a/analyzer/tests/unit/test_checker_handling.py b/analyzer/tests/unit/test_checker_handling.py index f3c521ed84..8f127127ae 100644 --- a/analyzer/tests/unit/test_checker_handling.py +++ b/analyzer/tests/unit/test_checker_handling.py @@ -24,6 +24,9 @@ from codechecker_analyzer.analyzers.config_handler import CheckerState from codechecker_analyzer.analyzers.clangtidy.config_handler \ import is_compiler_warning +from codechecker_analyzer.arg import AnalyzerConfig, CheckerConfig +from codechecker_analyzer.cmd.analyze import \ + validate_analyzer_parameter, validate_checker_parameter from codechecker_analyzer import analyzer_context from codechecker_analyzer.buildlog import log_parser @@ -396,6 +399,20 @@ def test_disable_clangsa_checkers(self): analyzer.config_handler.checks()['Wreserved-id-macro'][0], CheckerState.enabled) + def test_analyze_wrong_parameters(self): + """ + This test checks whether the analyze command detects if a wrong + --analyzer-config or --checker-config parameter is specified. + """ + + analyzer_cfg = [AnalyzerConfig('asd', '123', 'value')] + checker_cfg = [CheckerConfig('clangsa', 'asd', '123', 'value')] + + analyzer_func = validate_analyzer_parameter(analyzer_cfg) + checker_func = validate_checker_parameter(checker_cfg) + + [self.assertFalse(func) for func in [analyzer_func, checker_func]] + def test_enable_all_disable_warning(self): """ If --enable-all is used and a warning is disabled, then the proper