From 38abeccd24c0b4cc7da265c7f332ab1dfa9b6323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 21 Jan 2023 10:39:44 +0100 Subject: [PATCH] added command-line option `--disable=` to disable individual checks (#4712) --- Makefile | 4 ++ cli/cmdlineparser.cpp | 16 +++++- lib/settings.cpp | 54 +++++++++++++++--- lib/settings.h | 19 +++++++ man/cppcheck.1.xml | 12 ++++ releasenotes.txt | 1 + test/testcmdlineparser.cpp | 112 +++++++++++++++++++++++++++++++++++++ test/testrunner.vcxproj | 1 + test/testsettings.cpp | 90 +++++++++++++++++++++++++++++ 9 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 test/testsettings.cpp diff --git a/Makefile b/Makefile index 4ef48391d..f52239ab8 100644 --- a/Makefile +++ b/Makefile @@ -301,6 +301,7 @@ TESTOBJ = test/options.o \ test/testpreprocessor.o \ test/testprocessexecutor.o \ test/testrunner.o \ + test/testsettings.o \ test/testsimplifytemplate.o \ test/testsimplifytokens.o \ test/testsimplifytypedef.o \ @@ -768,6 +769,9 @@ test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/proc test/testrunner.o: test/testrunner.cpp externals/simplecpp/simplecpp.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/preprocessor.h lib/suppressions.h test/options.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testrunner.cpp +test/testsettings.o: test/testsettings.cpp lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h test/testsuite.h + $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsettings.cpp + test/testsimplifytemplate.o: test/testsimplifytemplate.cpp lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifytemplate.cpp diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 6de62e5c0..5e9c27c8c 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -299,6 +299,14 @@ bool CmdLineParser::parseFromArgs(int argc, const char* const argv[]) else if (std::strcmp(argv[i], "--debug-warnings") == 0) mSettings->debugwarnings = true; + else if (std::strncmp(argv[i], "--disable=", 10) == 0) { + const std::string errmsg = mSettings->removeEnabled(argv[i] + 10); + if (!errmsg.empty()) { + printError(errmsg); + return false; + } + } + // documentation.. else if (std::strcmp(argv[i], "--doc") == 0) { std::ostringstream doc; @@ -321,13 +329,14 @@ bool CmdLineParser::parseFromArgs(int argc, const char* const argv[]) mSettings->dump = true; else if (std::strncmp(argv[i], "--enable=", 9) == 0) { - const std::string errmsg = mSettings->addEnabled(argv[i] + 9); + const std::string enable_arg = argv[i] + 9; + const std::string errmsg = mSettings->addEnabled(enable_arg); if (!errmsg.empty()) { printError(errmsg); return false; } // when "style" is enabled, also enable "warning", "performance" and "portability" - if (mSettings->severity.isEnabled(Severity::style)) { + if (enable_arg.find("style") != std::string::npos) { mSettings->addEnabled("warning"); mSettings->addEnabled("performance"); mSettings->addEnabled("portability"); @@ -1084,6 +1093,9 @@ void CmdLineParser::printHelp() " be considered for evaluation.\n" " --config-excludes-file=\n" " A file that contains a list of config-excludes\n" + " --disable= Disable individual checks.\n" + " Please refer to the documentation of --enable=\n" + " for futher details.\n" " --dump Dump xml data for each translation unit. The dump\n" " files have the extension .dump and contain ast,\n" " tokenlist, symboldatabase, valueflow.\n" diff --git a/lib/settings.cpp b/lib/settings.cpp index db856c667..6c45ad9e3 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -113,7 +113,7 @@ void Settings::loadCppcheckCfg() } } -std::string Settings::addEnabled(const std::string &str) +std::string Settings::parseEnabled(const std::string &str, std::tuple, SimpleEnableGroup> &groups) { // Enable parameters may be comma separated... if (str.find(',') != std::string::npos) { @@ -122,7 +122,7 @@ std::string Settings::addEnabled(const std::string &str) while ((pos = str.find(',', pos)) != std::string::npos) { if (pos == prevPos) return std::string("--enable parameter is empty"); - const std::string errmsg(addEnabled(str.substr(prevPos, pos - prevPos))); + std::string errmsg(parseEnabled(str.substr(prevPos, pos - prevPos), groups)); if (!errmsg.empty()) return errmsg; ++pos; @@ -130,11 +130,18 @@ std::string Settings::addEnabled(const std::string &str) } if (prevPos >= str.length()) return std::string("--enable parameter is empty"); - return addEnabled(str.substr(prevPos)); + return parseEnabled(str.substr(prevPos), groups); } + auto& severity = std::get<0>(groups); + auto& checks = std::get<1>(groups); + if (str == "all") { - severity.fill(); + // "error" is always enabled and cannot be controlled - so exclude it from "all" + SimpleEnableGroup newSeverity; + newSeverity.fill(); + newSeverity.disable(Severity::SeverityType::error); + severity.enable(newSeverity); checks.enable(Checks::missingInclude); checks.enable(Checks::unusedFunction); } else if (str == "warning") { @@ -159,13 +166,46 @@ std::string Settings::addEnabled(const std::string &str) } #endif else { + // the actual option is prepending in the applyEnabled() call if (str.empty()) - return std::string("cppcheck: --enable parameter is empty"); + return " parameter is empty"; else - return std::string("cppcheck: there is no --enable parameter with the name '" + str + "'"); + return " parameter with the unknown name '" + str + "'"; } - return std::string(); + return ""; +} + +std::string Settings::addEnabled(const std::string &str) +{ + return applyEnabled(str, true); +} + +std::string Settings::removeEnabled(const std::string &str) +{ + return applyEnabled(str, false); +} + +std::string Settings::applyEnabled(const std::string &str, bool enable) +{ + std::tuple, SimpleEnableGroup> groups; + std::string errmsg = parseEnabled(str, groups); + if (!errmsg.empty()) + return (enable ? "--enable" : "--disable") + errmsg; + + const auto s = std::get<0>(groups); + const auto c = std::get<1>(groups); + if (enable) { + severity.enable(s); + checks.enable(c); + } + else { + severity.disable(s); + checks.disable(c); + } + // FIXME: hack to make sure "error" is always enabled + severity.enable(Severity::SeverityType::error); + return errmsg; } bool Settings::isEnabled(const ValueFlow::Value *value, bool inconclusiveCheck) const diff --git a/lib/settings.h b/lib/settings.h index 5cb4f3411..cf6f9873b 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -71,9 +72,15 @@ public: void enable(T flag) { mFlags |= (1U << (uint32_t)flag); } + void enable(SimpleEnableGroup group) { + mFlags |= group.intValue(); + } void disable(T flag) { mFlags &= ~(1U << (uint32_t)flag); } + void disable(SimpleEnableGroup group) { + mFlags &= ~(group.intValue()); + } void setEnabled(T flag, bool enabled) { if (enabled) enable(flag); @@ -393,6 +400,14 @@ public: */ std::string addEnabled(const std::string &str); + /** + * @brief Disable extra checks by id + * @param str single id or list of id values to be enabled + * or empty string to enable all. e.g. "style,possibleError" + * @return error message. empty upon success + */ + std::string removeEnabled(const std::string &str); + /** * @brief Returns true if given value can be shown * @return true if the value can be shown @@ -417,6 +432,10 @@ public: std::set summaryReturn; void loadSummaries(); + +private: + static std::string parseEnabled(const std::string &str, std::tuple, SimpleEnableGroup> &groups); + std::string applyEnabled(const std::string &str, bool enable); }; /// @} diff --git a/man/cppcheck.1.xml b/man/cppcheck.1.xml index 60e9e94ff..9f6914d0f 100644 --- a/man/cppcheck.1.xml +++ b/man/cppcheck.1.xml @@ -105,6 +105,9 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/ + + + @@ -272,6 +275,15 @@ Example: -DDEBUG=1 -D__cplusplus Example: '-UDEBUG' + + + + + + Disable individual checks. Please refer to the documentation of --enable=<id> for futher details. + + + diff --git a/releasenotes.txt b/releasenotes.txt index a536fc0a3..de0c34494 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -2,3 +2,4 @@ release notes for cppcheck-2.10 - the deprecated Makefile option SRCDIR is no longer accepted - if the file provided via "--file-list" cannot be opened it will now error out +- add command-line option "--disable=" to individually disable checks diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 2dee45329..e1001710c 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -88,6 +88,15 @@ private: TEST_CASE(enabledInternal); #endif TEST_CASE(enabledMultiple); + TEST_CASE(enabledInvalid); + TEST_CASE(enabledError); + TEST_CASE(enabledEmpty); + TEST_CASE(disableAll); + TEST_CASE(disableMultiple); + TEST_CASE(disableStylePartial); + TEST_CASE(disableInvalid); + TEST_CASE(disableError); + TEST_CASE(disableEmpty); TEST_CASE(inconclusive); TEST_CASE(errorExitcode); TEST_CASE(errorExitcodeMissing); @@ -654,6 +663,109 @@ private: ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); } + void enabledInvalid() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--enable=warning,missingIncludeSystem,style", "file.cpp"}; + settings = Settings(); + ASSERT(!defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: --enable parameter with the unknown name 'missingIncludeSystem'\n", GET_REDIRECT_OUTPUT); + } + + void enabledError() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--enable=error", "file.cpp"}; + settings = Settings(); + ASSERT(!defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: --enable parameter with the unknown name 'error'\n", GET_REDIRECT_OUTPUT); + } + + void enabledEmpty() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--enable=", "file.cpp"}; + settings = Settings(); + ASSERT(!defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: --enable parameter is empty\n", GET_REDIRECT_OUTPUT); + } + + void disableAll() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--enable=all", "--disable=all"}; + settings.severity.clear(); + settings.checks.clear(); + ASSERT(defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::error)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::warning)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::style)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::performance)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::portability)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::debug)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::internalCheck)); + ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + + void disableMultiple() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--enable=all", "--disable=style", "--disable=unusedFunction"}; + settings.severity.clear(); + settings.checks.clear(); + ASSERT(defParser.parseFromArgs(4, argv)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::error)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::warning)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::style)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::performance)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::portability)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::debug)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(true, settings.checks.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::internalCheck)); + ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + + // make sure the implied "style" checks are not added when "--enable=style" is specified + void disableStylePartial() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--enable=style", "--disable=performance", "--enable=unusedFunction"}; + settings.severity.clear(); + settings.checks.clear(); + ASSERT(defParser.parseFromArgs(4, argv)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::error)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::warning)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::style)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::performance)); + ASSERT_EQUALS(true, settings.severity.isEnabled(Severity::portability)); + ASSERT_EQUALS(false, settings.severity.isEnabled(Severity::debug)); + ASSERT_EQUALS(true, settings.checks.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, settings.checks.isEnabled(Checks::internalCheck)); + ASSERT_EQUALS("", GET_REDIRECT_OUTPUT); + } + + void disableInvalid() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--disable=leaks", "file.cpp"}; + settings = Settings(); + ASSERT(!defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: --disable parameter with the unknown name 'leaks'\n", GET_REDIRECT_OUTPUT); + } + + void disableError() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--disable=error", "file.cpp"}; + settings = Settings(); + ASSERT(!defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: --disable parameter with the unknown name 'error'\n", GET_REDIRECT_OUTPUT); + } + + void disableEmpty() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--disable=", "file.cpp"}; + settings = Settings(); + ASSERT(!defParser.parseFromArgs(3, argv)); + ASSERT_EQUALS("cppcheck: error: --disable parameter is empty\n", GET_REDIRECT_OUTPUT); + } + void inconclusive() { REDIRECT; const char * const argv[] = {"cppcheck", "--inconclusive"}; diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index 8df77ca9c..9e4b48c71 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -71,6 +71,7 @@ + diff --git a/test/testsettings.cpp b/test/testsettings.cpp new file mode 100644 index 000000000..e186299f6 --- /dev/null +++ b/test/testsettings.cpp @@ -0,0 +1,90 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "errortypes.h" +#include "settings.h" +#include "testsuite.h" + +class TestSettings : public TestFixture { +public: + TestSettings() : TestFixture("TestSettings") {} + +private: + void run() override { + TEST_CASE(simpleEnableGroup); + } + + void simpleEnableGroup() const { + SimpleEnableGroup group; + + ASSERT_EQUALS(0, group.intValue()); + ASSERT_EQUALS(false, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(false, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); + + group.fill(); + + ASSERT_EQUALS(4294967295, group.intValue()); + ASSERT_EQUALS(true, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(true, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(true, group.isEnabled(Checks::internalCheck)); + + group.clear(); + + ASSERT_EQUALS(0, group.intValue()); + ASSERT_EQUALS(false, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(false, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); + + group.enable(Checks::unusedFunction); + group.setEnabled(Checks::missingInclude, true); + + ASSERT_EQUALS(3, group.intValue()); + ASSERT_EQUALS(true, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(true, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); + + group.disable(Checks::unusedFunction); + group.setEnabled(Checks::missingInclude, false); + + ASSERT_EQUALS(0, group.intValue()); + ASSERT_EQUALS(false, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(false, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); + + SimpleEnableGroup newGroup; + newGroup.enable(Checks::missingInclude); + + group.enable(newGroup); + + ASSERT_EQUALS(2, group.intValue()); + ASSERT_EQUALS(false, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(true, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); + + group.disable(newGroup); + + ASSERT_EQUALS(0, group.intValue()); + ASSERT_EQUALS(false, group.isEnabled(Checks::unusedFunction)); + ASSERT_EQUALS(false, group.isEnabled(Checks::missingInclude)); + ASSERT_EQUALS(false, group.isEnabled(Checks::internalCheck)); + } +}; + +REGISTER_TEST(TestSettings)