From 70cae5362f118caa263ef3693511014de7c3cb35 Mon Sep 17 00:00:00 2001 From: bruntib Date: Fri, 4 Aug 2023 16:25:40 +0200 Subject: [PATCH] [bugfix] Allow statisticsbased checker disable When --stats flag is given to then command "CodeChecker analyze" then two statistics-based checkers are enabled by default: - statisticsbased.SpecialReturnValue - statisticsbased.UncheckedReturnValue These can't be disabled if --stats flag is used. This patch fixes this bug: now users can disable either statistics-based checker, even if --stats is used. --- analyzer/codechecker_analyzer/analyzer.py | 21 -------- .../analyzers/clangsa/analyzer.py | 51 +++++++++++++++++++ analyzer/tests/unit/test_checker_handling.py | 26 ++++++++++ 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/analyzer/codechecker_analyzer/analyzer.py b/analyzer/codechecker_analyzer/analyzer.py index d9fb71009c..55b406a772 100644 --- a/analyzer/codechecker_analyzer/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzer.py @@ -20,11 +20,6 @@ from codechecker_common.logger import get_logger -from codechecker_statistics_collector.collectors.special_return_value import \ - SpecialReturnValueCollector -from codechecker_statistics_collector.collectors.return_value import \ - ReturnValueCollector - from . import analyzer_context, analysis_manager, pre_analysis_manager, \ checkers from .analyzers import analyzer_types @@ -193,22 +188,6 @@ def perform_analysis(args, skip_handlers, actions, metadata_tool, "'--no-missing-checker-error'") sys.exit(1) - if 'stats_enabled' in args: - config_map[ClangSA.ANALYZER_NAME].set_checker_enabled( - SpecialReturnValueCollector.checker_analyze) - - config_map[ClangSA.ANALYZER_NAME].set_checker_enabled( - ReturnValueCollector.checker_analyze) - - # Statistics collector checkers must be explicitly disabled - # as they trash the output. - if "clangsa" in analyzers: - config_map[ClangSA.ANALYZER_NAME].set_checker_enabled( - SpecialReturnValueCollector.checker_collect, False) - - config_map[ClangSA.ANALYZER_NAME].set_checker_enabled( - ReturnValueCollector.checker_collect, False) - enabled_checkers = defaultdict(list) # Save some metadata information. diff --git a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py index 8fc231e9e8..f05ba93a2c 100644 --- a/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzers/clangsa/analyzer.py @@ -19,6 +19,10 @@ from codechecker_common.logger import get_logger from codechecker_analyzer import analyzer_context, env +from codechecker_statistics_collector.collectors.special_return_value import \ + SpecialReturnValueCollector +from codechecker_statistics_collector.collectors.return_value import \ + ReturnValueCollector from .. import analyzer_base from ..config_handler import CheckerState @@ -94,6 +98,19 @@ def parse_clang_help_page( return res +def _is_user_disabled_checker(checker, ordered_checkers): + """ + This function returns True if the given checker is disabled by the user + explicitly by a --disable flag. + """ + if not ordered_checkers: + return False + + disabled_checkers = (c for c, enabled in ordered_checkers if not enabled) + + return any(checker.startswith(c) for c in disabled_checkers) + + class ClangSA(analyzer_base.SourceAnalyzer): """ Constructs clang static analyzer commands. @@ -596,6 +613,40 @@ def construct_config_handler(cls, args): cmdline_checkers, 'enable_all' in args and args.enable_all) + # If --stats flag is provided then enable statistics-based checkers + # unless explicitly disabled by the user. + if 'stats_enabled' in args: + # In some Clang versions, statisticsbased checkers are in the alpha + # package (alpha.statisticsbased), and maybe not in others. Lets + # figure out the actual checker name first. + special_return_value = next(( + checker for checker, _ in checkers + if SpecialReturnValueCollector.checker_analyze in checker), + None) + unchecked_return_value = next(( + checker for checker, _ in checkers + if ReturnValueCollector.checker_analyze in checker), + None) + + if special_return_value and not _is_user_disabled_checker( + special_return_value, + cmdline_checkers): + handler.set_checker_enabled(special_return_value) + + if unchecked_return_value and not _is_user_disabled_checker( + unchecked_return_value, + cmdline_checkers): + handler.set_checker_enabled(unchecked_return_value) + + # Statistics collector checkers must be explicitly disabled as they + # trash the output. Thise checkers are executed in a preceding analysis + # phase. + handler.set_checker_enabled( + SpecialReturnValueCollector.checker_collect, False) + + handler.set_checker_enabled( + ReturnValueCollector.checker_collect, False) + handler.checker_config = [] # TODO: This extra "isinstance" check is needed for diff --git a/analyzer/tests/unit/test_checker_handling.py b/analyzer/tests/unit/test_checker_handling.py index fa02da5bac..fe5d23d494 100644 --- a/analyzer/tests/unit/test_checker_handling.py +++ b/analyzer/tests/unit/test_checker_handling.py @@ -11,6 +11,8 @@ """ +from distutils import util +import os import unittest from argparse import Namespace @@ -148,10 +150,15 @@ def f(checks, checkers): 'security.insecureAPI.bcmp', 'alpha.llvm.Conventions'] + statisticsbased = [ + 'statisticsbased.SpecialReturnValue', + 'statisticsbased.UncheckedReturnValue'] + checkers = [] checkers.extend(map(add_description, security_profile_alpha)) checkers.extend(map(add_description, default_profile)) checkers.extend(map(add_description, cert_guideline)) + checkers.extend(map(add_description, statisticsbased)) # "default" profile checkers are enabled explicitly. Others are in # "default" state. @@ -232,6 +239,25 @@ def f(checks, checkers): self.assertTrue(all_with_status(CheckerState.enabled) (cfg_handler.checks(), low_severity)) + # Test if statisticsbased checkers are enabled by --stats flag + # by default. + stats_capable = bool(util.strtobool( + os.environ.get('CC_TEST_FORCE_STATS_CAPABLE', 'False'))) + + if stats_capable: + cfg_handler = ClangSA.construct_config_handler( + Namespace(stats_enabled=True)) + cfg_handler.initialize_checkers(checkers, []) + + enabled_checkers = ( + checker for checker, (enabled, _) + in cfg_handler.checks().items() + if enabled == CheckerState.enabled) + + for stat_checker in statisticsbased: + self.assertTrue( + any(stat_checker in c for c in enabled_checkers)) + def create_analyzer_tidy(args=[]): cfg_handler = ClangTidy.construct_config_handler(args)