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/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/lib/cppcheck.cpp b/lib/cppcheck.cpp index a1669edeae8..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"); @@ -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 903a4414cd6..141a4a8d34e 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -4051,4 +4051,71 @@ 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): + __test_active_checkers(tmp_path, 1, 966, use_unusedfunction_only=True) + + +def test_active_unusedfunction_only_builddir(tmp_path): + checkers_exp = [ + 'CheckUnusedFunctions::check' + ] + __test_active_checkers(tmp_path, 1, 966, use_unusedfunction_only=True, checkers_exp=checkers_exp) + + +def test_active_unusedfunction_only_misra(tmp_path): + __test_active_checkers(tmp_path, 1, 1166, use_unusedfunction_only=True, use_misra=True) + + +def test_active_unusedfunction_only_misra_builddir(tmp_path): + checkers_exp = [ + 'CheckUnusedFunctions::check' + ] + __test_active_checkers(tmp_path, 1, 1166, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp)