From ece8f8eb08e1eb7ec99ef2b38e3a743d4686ffe5 Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 1 Dec 2025 15:18:38 +0100 Subject: [PATCH 1/4] refs #14304 - test/cli/other_test.py: added tests of active checker counts --- test/cli/other_test.py | 213 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 1 deletion(-) diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 903a4414cd6..4a511499794 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4051,4 +4051,215 @@ def test_no_valid_configuration_check_config(tmp_path): assert stderr.splitlines() == [ '{}:1:2: error: No header in #include [syntaxError]'.format(test_file), '{}:1:2: error: No header in #include [syntaxError]'.format(test_file) - ] \ No newline at end of file + ] + + +def __test_active_checkers(tmp_path, active_cnt, total_cnt, use_misra=False, use_unusedfunction_only=False, checkers_exp=None): + test_file = tmp_path / 'test.c' + with open(test_file, 'w') as f: + f.write('int i;') + + build_dir = None + if checkers_exp is not None: + build_dir = tmp_path / 'b1' + os.makedirs(build_dir) + + args = [ + '-q', + '--enable=information', + '-j1', + str(test_file) + ] + + if use_misra: + args += ['--addon=misra'] + if build_dir: + args += ['--cppcheck-build-dir={}'.format(build_dir)] + else: + args += ['--no-cppcheck-build-dir'] + + env = {} + if use_unusedfunction_only: + env = {'UNUSEDFUNCTION_ONLY': '1'} + args += ['--enable=unusedFunction'] + exitcode, stdout, stderr, _ = cppcheck_ex(args, remove_checkers_report=False, env=env) + assert exitcode == 0, stdout + assert stdout.splitlines() == [] + assert stderr.splitlines() == [ + f'nofile:0:0: information: Active checkers: {active_cnt}/{total_cnt} (use --checkers-report= to see details) [checkersReport]', + '' # TODO: get rid of extra newline + ] + + if build_dir: + checkers_file = build_dir / 'checkers.txt' + with open(checkers_file, 'r') as f: + checkers = f.read().splitlines() + + assert checkers == checkers_exp + assert len(checkers) == active_cnt + + +def test_active_unusedfunction_only(tmp_path): + # TODO: should only report a single active check + __test_active_checkers(tmp_path, 5, 966, use_unusedfunction_only=True) + + +def test_active_unusedfunction_only_builddir(tmp_path): + # TODO: should only report a single active check + checkers_exp = [ + 'CheckBufferOverrun::analyseWholeProgram', + 'CheckClass::analyseWholeProgram', + 'CheckNullPointer::analyseWholeProgram', + 'CheckUninitVar::analyseWholeProgram', + 'CheckUnusedFunctions::check', + ] + __test_active_checkers(tmp_path, 5, 966, use_unusedfunction_only=True, checkers_exp=checkers_exp) + + +def test_active_unusedfunction_only_misra(tmp_path): + # TODO: should only report a single active check + __test_active_checkers(tmp_path, 267, 1166, use_unusedfunction_only=True, use_misra=True) + + +@pytest.mark.xfail(strict=True) # TODO: active count and checkers.txt differ +def test_active_unusedfunction_only_misra_builddir(tmp_path): + # TODO: should only report a single active check + checkers_exp = [ + 'CheckBufferOverrun::analyseWholeProgram', + 'CheckClass::analyseWholeProgram', + 'CheckNullPointer::analyseWholeProgram', + 'CheckUninitVar::analyseWholeProgram', + 'CheckUnusedFunctions::check', + 'Misra C: 1.2', + 'Misra C: 1.4', + 'Misra C: 10.1', + 'Misra C: 10.2', + 'Misra C: 10.3', + 'Misra C: 10.4', + 'Misra C: 10.5', + 'Misra C: 10.6', + 'Misra C: 10.7', + 'Misra C: 10.8', + 'Misra C: 11.1', + 'Misra C: 11.2', + 'Misra C: 11.3', + 'Misra C: 11.4', + 'Misra C: 11.5', + 'Misra C: 11.6', + 'Misra C: 11.7', + 'Misra C: 11.8', + 'Misra C: 11.9', + 'Misra C: 12.1', + 'Misra C: 12.2', + 'Misra C: 12.3', + 'Misra C: 12.4', + 'Misra C: 13.1', + 'Misra C: 13.3', + 'Misra C: 13.4', + 'Misra C: 13.5', + 'Misra C: 13.6', + 'Misra C: 14.1', + 'Misra C: 14.2', + 'Misra C: 14.4', + 'Misra C: 15.1', + 'Misra C: 15.2', + 'Misra C: 15.3', + 'Misra C: 15.4', + 'Misra C: 15.5', + 'Misra C: 15.6', + 'Misra C: 15.7', + 'Misra C: 16.1', + 'Misra C: 16.2', + 'Misra C: 16.3', + 'Misra C: 16.4', + 'Misra C: 16.5', + 'Misra C: 16.6', + 'Misra C: 16.7', + 'Misra C: 17.1', + 'Misra C: 17.2', + 'Misra C: 17.3', + 'Misra C: 17.6', + 'Misra C: 17.7', + 'Misra C: 17.8', + 'Misra C: 18.4', + 'Misra C: 18.5', + 'Misra C: 18.7', + 'Misra C: 18.8', + 'Misra C: 19.2', + 'Misra C: 2.2', + 'Misra C: 2.3', + 'Misra C: 2.4', + 'Misra C: 2.5', + 'Misra C: 2.7', + 'Misra C: 20.1', + 'Misra C: 20.10', + 'Misra C: 20.11', + 'Misra C: 20.12', + 'Misra C: 20.13', + 'Misra C: 20.14', + 'Misra C: 20.2', + 'Misra C: 20.3', + 'Misra C: 20.4', + 'Misra C: 20.5', + 'Misra C: 20.7', + 'Misra C: 20.8', + 'Misra C: 20.9', + 'Misra C: 21.1', + 'Misra C: 21.10', + 'Misra C: 21.11', + 'Misra C: 21.12', + 'Misra C: 21.14', + 'Misra C: 21.15', + 'Misra C: 21.16', + 'Misra C: 21.19', + 'Misra C: 21.2', + 'Misra C: 21.20', + 'Misra C: 21.21', + 'Misra C: 21.3', + 'Misra C: 21.4', + 'Misra C: 21.5', + 'Misra C: 21.6', + 'Misra C: 21.7', + 'Misra C: 21.8', + 'Misra C: 21.9', + 'Misra C: 22.10', + 'Misra C: 22.5', + 'Misra C: 22.7', + 'Misra C: 22.8', + 'Misra C: 22.9', + 'Misra C: 3.1', + 'Misra C: 4.1', + 'Misra C: 4.2', + 'Misra C: 5.1', + 'Misra C: 5.2', + 'Misra C: 5.4', + 'Misra C: 5.5', + 'Misra C: 5.6', + 'Misra C: 5.7', + 'Misra C: 5.8', + 'Misra C: 5.9', + 'Misra C: 6.1', + 'Misra C: 6.2', + 'Misra C: 7.1', + 'Misra C: 7.2', + 'Misra C: 7.3', + 'Misra C: 7.4', + 'Misra C: 8.1', + 'Misra C: 8.10', + 'Misra C: 8.11', + 'Misra C: 8.12', + 'Misra C: 8.14', + 'Misra C: 8.2', + 'Misra C: 8.4', + 'Misra C: 8.5', + 'Misra C: 8.6', + 'Misra C: 8.7', + 'Misra C: 8.8', + 'Misra C: 8.9', + 'Misra C: 9.2', + 'Misra C: 9.3', + 'Misra C: 9.4', + 'Misra C: 9.5' + ] + __test_active_checkers(tmp_path, 267, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) + From 48cd90f8ae4149195500a67cbba2872614fd3abf Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 1 Dec 2025 15:35:06 +0100 Subject: [PATCH 2/4] do not perform whole program analysis with `UNUSEDFUNCTION_ONLY` hack --- cli/singleexecutor.cpp | 1 + lib/cppcheck.cpp | 41 ++++++++++++++++++++++++----------------- test/cli/other_test.py | 20 +++++--------------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/cli/singleexecutor.cpp b/cli/singleexecutor.cpp index 54cab1cb8d8..2dec1b6ad14 100644 --- a/cli/singleexecutor.cpp +++ b/cli/singleexecutor.cpp @@ -67,6 +67,7 @@ unsigned int SingleExecutor::check() reportStatus(c, mFileSettings.size(), c, mFileSettings.size()); } + // TODO: CppCheckExecutor::check_internal() is also invoking the whole program analysis - is it run twice? if (mCppcheck.analyseWholeProgram()) result++; diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index a1669edeae8..697d6f46cd8 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1807,22 +1807,25 @@ void CppCheck::analyseClangTidy(const FileSettings &fileSettings) bool CppCheck::analyseWholeProgram() { bool errors = false; - // Analyse the tokens - CTU::FileInfo ctu; - if (mSettings.useSingleJob() || !mSettings.buildDir.empty()) - { - for (const Check::FileInfo *fi : mFileInfo) { - const auto *fi2 = dynamic_cast(fi); - if (fi2) { - ctu.functionCalls.insert(ctu.functionCalls.end(), fi2->functionCalls.cbegin(), fi2->functionCalls.cend()); - ctu.nestedCalls.insert(ctu.nestedCalls.end(), fi2->nestedCalls.cbegin(), fi2->nestedCalls.cend()); + + if (!Settings::unusedFunctionOnly()) { + // Analyse the tokens + CTU::FileInfo ctu; + if (mSettings.useSingleJob() || !mSettings.buildDir.empty()) + { + for (const Check::FileInfo *fi : mFileInfo) { + const auto *fi2 = dynamic_cast(fi); + if (fi2) { + ctu.functionCalls.insert(ctu.functionCalls.end(), fi2->functionCalls.cbegin(), fi2->functionCalls.cend()); + ctu.nestedCalls.insert(ctu.nestedCalls.end(), fi2->nestedCalls.cbegin(), fi2->nestedCalls.cend()); + } } } - } - // cppcheck-suppress shadowFunction - TODO: fix this - for (Check *check : Check::instances()) - errors |= check->analyseWholeProgram(ctu, mFileInfo, mSettings, mErrorLogger); // TODO: ctu + // cppcheck-suppress shadowFunction - TODO: fix this + for (Check *check : Check::instances()) + errors |= check->analyseWholeProgram(ctu, mFileInfo, mSettings, mErrorLogger); // TODO: ctu + } if (mUnusedFunctionsCheck) errors |= mUnusedFunctionsCheck->check(mSettings, mErrorLogger); @@ -1832,9 +1835,16 @@ bool CppCheck::analyseWholeProgram() unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const std::list &files, const std::list& fileSettings, const std::string& ctuInfo) { - executeAddonsWholeProgram(files, fileSettings, ctuInfo); if (mSettings.checks.isEnabled(Checks::unusedFunction)) CheckUnusedFunctions::analyseWholeProgram(mSettings, mErrorLogger, buildDir); + + if (mUnusedFunctionsCheck) + mUnusedFunctionsCheck->check(mSettings, mErrorLogger); + + if (Settings::unusedFunctionOnly()) + return mLogger->exitcode(); + + executeAddonsWholeProgram(files, fileSettings, ctuInfo); std::list fileInfoList; CTU::FileInfo ctuFileInfo; @@ -1885,9 +1895,6 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st for (Check *check : Check::instances()) check->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); - if (mUnusedFunctionsCheck) - mUnusedFunctionsCheck->check(mSettings, mErrorLogger); - for (Check::FileInfo *fi : fileInfoList) delete fi; diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 4a511499794..620377aa020 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4100,35 +4100,26 @@ def __test_active_checkers(tmp_path, active_cnt, total_cnt, use_misra=False, use def test_active_unusedfunction_only(tmp_path): - # TODO: should only report a single active check - __test_active_checkers(tmp_path, 5, 966, use_unusedfunction_only=True) + __test_active_checkers(tmp_path, 1, 966, use_unusedfunction_only=True) def test_active_unusedfunction_only_builddir(tmp_path): # TODO: should only report a single active check checkers_exp = [ - 'CheckBufferOverrun::analyseWholeProgram', - 'CheckClass::analyseWholeProgram', - 'CheckNullPointer::analyseWholeProgram', - 'CheckUninitVar::analyseWholeProgram', - 'CheckUnusedFunctions::check', + 'CheckUnusedFunctions::check' ] - __test_active_checkers(tmp_path, 5, 966, use_unusedfunction_only=True, checkers_exp=checkers_exp) + __test_active_checkers(tmp_path, 1, 966, use_unusedfunction_only=True, checkers_exp=checkers_exp) def test_active_unusedfunction_only_misra(tmp_path): # TODO: should only report a single active check - __test_active_checkers(tmp_path, 267, 1166, use_unusedfunction_only=True, use_misra=True) + __test_active_checkers(tmp_path, 263, 1166, use_unusedfunction_only=True, use_misra=True) @pytest.mark.xfail(strict=True) # TODO: active count and checkers.txt differ def test_active_unusedfunction_only_misra_builddir(tmp_path): # TODO: should only report a single active check checkers_exp = [ - 'CheckBufferOverrun::analyseWholeProgram', - 'CheckClass::analyseWholeProgram', - 'CheckNullPointer::analyseWholeProgram', - 'CheckUninitVar::analyseWholeProgram', 'CheckUnusedFunctions::check', 'Misra C: 1.2', 'Misra C: 1.4', @@ -4261,5 +4252,4 @@ def test_active_unusedfunction_only_misra_builddir(tmp_path): 'Misra C: 9.4', 'Misra C: 9.5' ] - __test_active_checkers(tmp_path, 267, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) - + __test_active_checkers(tmp_path, 263, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) \ No newline at end of file From bbd133420793f67151d75099388e8aa4d998e896 Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 1 Dec 2025 15:45:12 +0100 Subject: [PATCH 3/4] do not execute addons with `UNUSEDFUNCTION_ONLY` hack --- lib/cppcheck.cpp | 2 +- test/cli/other_test.py | 139 ++--------------------------------------- 2 files changed, 5 insertions(+), 136 deletions(-) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 697d6f46cd8..9490f62475d 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1495,7 +1495,7 @@ void CppCheck::executeAddons(const std::string& dumpFile, const FileWithDetails& void CppCheck::executeAddons(const std::vector& files, const std::string& file0) { - if (mSettings.addons.empty() || files.empty()) + if (mSettings.addons.empty() || files.empty() || Settings::unusedFunctionOnly()) return; const bool isCtuInfo = endsWith(files[0], ".ctu-info"); diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 620377aa020..f65d1a5e54c 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4104,7 +4104,6 @@ def test_active_unusedfunction_only(tmp_path): def test_active_unusedfunction_only_builddir(tmp_path): - # TODO: should only report a single active check checkers_exp = [ 'CheckUnusedFunctions::check' ] @@ -4113,143 +4112,13 @@ def test_active_unusedfunction_only_builddir(tmp_path): def test_active_unusedfunction_only_misra(tmp_path): # TODO: should only report a single active check - __test_active_checkers(tmp_path, 263, 1166, use_unusedfunction_only=True, use_misra=True) + __test_active_checkers(tmp_path, 7, 1166, use_unusedfunction_only=True, use_misra=True) @pytest.mark.xfail(strict=True) # TODO: active count and checkers.txt differ def test_active_unusedfunction_only_misra_builddir(tmp_path): # TODO: should only report a single active check checkers_exp = [ - 'CheckUnusedFunctions::check', - 'Misra C: 1.2', - 'Misra C: 1.4', - 'Misra C: 10.1', - 'Misra C: 10.2', - 'Misra C: 10.3', - 'Misra C: 10.4', - 'Misra C: 10.5', - 'Misra C: 10.6', - 'Misra C: 10.7', - 'Misra C: 10.8', - 'Misra C: 11.1', - 'Misra C: 11.2', - 'Misra C: 11.3', - 'Misra C: 11.4', - 'Misra C: 11.5', - 'Misra C: 11.6', - 'Misra C: 11.7', - 'Misra C: 11.8', - 'Misra C: 11.9', - 'Misra C: 12.1', - 'Misra C: 12.2', - 'Misra C: 12.3', - 'Misra C: 12.4', - 'Misra C: 13.1', - 'Misra C: 13.3', - 'Misra C: 13.4', - 'Misra C: 13.5', - 'Misra C: 13.6', - 'Misra C: 14.1', - 'Misra C: 14.2', - 'Misra C: 14.4', - 'Misra C: 15.1', - 'Misra C: 15.2', - 'Misra C: 15.3', - 'Misra C: 15.4', - 'Misra C: 15.5', - 'Misra C: 15.6', - 'Misra C: 15.7', - 'Misra C: 16.1', - 'Misra C: 16.2', - 'Misra C: 16.3', - 'Misra C: 16.4', - 'Misra C: 16.5', - 'Misra C: 16.6', - 'Misra C: 16.7', - 'Misra C: 17.1', - 'Misra C: 17.2', - 'Misra C: 17.3', - 'Misra C: 17.6', - 'Misra C: 17.7', - 'Misra C: 17.8', - 'Misra C: 18.4', - 'Misra C: 18.5', - 'Misra C: 18.7', - 'Misra C: 18.8', - 'Misra C: 19.2', - 'Misra C: 2.2', - 'Misra C: 2.3', - 'Misra C: 2.4', - 'Misra C: 2.5', - 'Misra C: 2.7', - 'Misra C: 20.1', - 'Misra C: 20.10', - 'Misra C: 20.11', - 'Misra C: 20.12', - 'Misra C: 20.13', - 'Misra C: 20.14', - 'Misra C: 20.2', - 'Misra C: 20.3', - 'Misra C: 20.4', - 'Misra C: 20.5', - 'Misra C: 20.7', - 'Misra C: 20.8', - 'Misra C: 20.9', - 'Misra C: 21.1', - 'Misra C: 21.10', - 'Misra C: 21.11', - 'Misra C: 21.12', - 'Misra C: 21.14', - 'Misra C: 21.15', - 'Misra C: 21.16', - 'Misra C: 21.19', - 'Misra C: 21.2', - 'Misra C: 21.20', - 'Misra C: 21.21', - 'Misra C: 21.3', - 'Misra C: 21.4', - 'Misra C: 21.5', - 'Misra C: 21.6', - 'Misra C: 21.7', - 'Misra C: 21.8', - 'Misra C: 21.9', - 'Misra C: 22.10', - 'Misra C: 22.5', - 'Misra C: 22.7', - 'Misra C: 22.8', - 'Misra C: 22.9', - 'Misra C: 3.1', - 'Misra C: 4.1', - 'Misra C: 4.2', - 'Misra C: 5.1', - 'Misra C: 5.2', - 'Misra C: 5.4', - 'Misra C: 5.5', - 'Misra C: 5.6', - 'Misra C: 5.7', - 'Misra C: 5.8', - 'Misra C: 5.9', - 'Misra C: 6.1', - 'Misra C: 6.2', - 'Misra C: 7.1', - 'Misra C: 7.2', - 'Misra C: 7.3', - 'Misra C: 7.4', - 'Misra C: 8.1', - 'Misra C: 8.10', - 'Misra C: 8.11', - 'Misra C: 8.12', - 'Misra C: 8.14', - 'Misra C: 8.2', - 'Misra C: 8.4', - 'Misra C: 8.5', - 'Misra C: 8.6', - 'Misra C: 8.7', - 'Misra C: 8.8', - 'Misra C: 8.9', - 'Misra C: 9.2', - 'Misra C: 9.3', - 'Misra C: 9.4', - 'Misra C: 9.5' - ] - __test_active_checkers(tmp_path, 263, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) \ No newline at end of file + 'CheckUnusedFunctions::check' + ] + __test_active_checkers(tmp_path, 7, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) From aa09401146a7c517e481d8bcc092a6a5f30fe6c9 Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 1 Dec 2025 15:58:47 +0100 Subject: [PATCH 4/4] CheckersReport: added workaround for "always active" checkers with `UNUSEDFUNCTION_ONLY` hack --- lib/checkersreport.cpp | 6 +++++- test/cli/other_test.py | 7 ++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/checkersreport.cpp b/lib/checkersreport.cpp index 3d9cb7c654f..c2b91c84ecb 100644 --- a/lib/checkersreport.cpp +++ b/lib/checkersreport.cpp @@ -143,9 +143,13 @@ void CheckersReport::countCheckers() ++mAllCheckersCount; } if (mSettings.premiumArgs.find("misra-c-") != std::string::npos || mSettings.addons.count("misra")) { + const bool doUnusedFunctionOnly = Settings::unusedFunctionOnly(); for (const checkers::MisraInfo& info: checkers::misraC2012Rules) { const std::string rule = std::to_string(info.a) + "." + std::to_string(info.b); - const bool active = isMisraRuleActive(mActiveCheckers, rule); + // this will return some rules as always active even if they are not in the active checkers. + // this leads to a difference in the shown count and in the checkers stored in the builddir + // TODO: fix this? + const bool active = !doUnusedFunctionOnly && isMisraRuleActive(mActiveCheckers, rule); if (active) ++mActiveCheckersCount; ++mAllCheckersCount; diff --git a/test/cli/other_test.py b/test/cli/other_test.py index f65d1a5e54c..141a4a8d34e 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4111,14 +4111,11 @@ def test_active_unusedfunction_only_builddir(tmp_path): def test_active_unusedfunction_only_misra(tmp_path): - # TODO: should only report a single active check - __test_active_checkers(tmp_path, 7, 1166, use_unusedfunction_only=True, use_misra=True) + __test_active_checkers(tmp_path, 1, 1166, use_unusedfunction_only=True, use_misra=True) -@pytest.mark.xfail(strict=True) # TODO: active count and checkers.txt differ def test_active_unusedfunction_only_misra_builddir(tmp_path): - # TODO: should only report a single active check checkers_exp = [ 'CheckUnusedFunctions::check' ] - __test_active_checkers(tmp_path, 7, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) + __test_active_checkers(tmp_path, 1, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp)