diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 8e32e3e3152..7d3f549e957 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1106,8 +1106,16 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str } if (mSettings.checkConfiguration) { - for (const std::string &config : configurations) - (void)preprocessor.getcode(config, files, false); + for (const std::string &config : configurations) { + simplecpp::OutputList outputList_cfg; + const simplecpp::TokenList tokensP = preprocessor.preprocess(config, files, outputList_cfg); + const simplecpp::Output* output = preprocessor.handleErrors(outputList_cfg); + // Other failures, and #error with explicit defines, are already reported by handleErrors. + if (output && output->type == simplecpp::Output::ERROR && startsWith(output->msg, "#error") && + (mSettings.userDefines.empty() || mSettings.force)) { + invalidConfigurationMessage(file.spath(), tokensP.file(output->location), config, *output); + } + } if (configurations.size() > maxConfigs) tooManyConfigsError(Path::toNativeSeparators(file.spath()), configurations.size()); @@ -1752,6 +1760,33 @@ void CppCheck::purgedConfigurationMessage(const std::string &file, const std::st mErrorLogger.reportErr(errmsg); } +void CppCheck::invalidConfigurationMessage(const std::string& file0, const std::string& file, + const std::string& configuration, const simplecpp::Output& output) +{ + std::list locations; + if (!file.empty()) { + std::string filename = Path::fromNativeSeparators(file); + if (mSettings.relativePaths) + filename = Path::getRelativePath(filename, mSettings.basePaths); + locations.emplace_back(filename, output.location.line, output.location.col); + } + + // preprocess() also applies userDefines; include them in the configuration shown to the user. + std::string effectiveConfig = mSettings.userDefines; + const std::vector userDefines = split(mSettings.userDefines, ";"); + for (const std::string& define : split(configuration, ";")) { + if (define.empty() || std::find(userDefines.cbegin(), userDefines.cend(), define) != userDefines.cend()) + continue; + if (!effectiveConfig.empty()) + effectiveConfig += ';'; + effectiveConfig += define; + } + + mErrorLogger.reportErr(ErrorMessage(std::move(locations), file0, Severity::information, + "The configuration '" + effectiveConfig + "' was not checked because of a preprocessor error: " + output.msg, + "invalidConfiguration", Certainty::normal)); +} + //--------------------------------------------------------------------------- void CppCheck::getErrorMessages(ErrorLogger &errorlogger) @@ -1763,6 +1798,7 @@ void CppCheck::getErrorMessages(ErrorLogger &errorlogger) CppCheck cppcheck(settings, supprs, errorlogger, nullptr, true, nullptr); cppcheck.purgedConfigurationMessage("",""); cppcheck.tooManyConfigsError("",0U); + cppcheck.invalidConfigurationMessage("", "", "", simplecpp::Output(simplecpp::Output::ERROR, {}, "#error")); // TODO: add functions to get remaining error messages Settings s; diff --git a/lib/cppcheck.h b/lib/cppcheck.h index 4ccd68193ea..c0177236d5e 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -146,6 +146,8 @@ class CPPCHECKLIB CppCheck { private: void purgedConfigurationMessage(const std::string &file, const std::string& configuration); + void invalidConfigurationMessage(const std::string& file0, const std::string& file, + const std::string& configuration, const simplecpp::Output& output); bool isPremiumCodingStandardId(const std::string& id) const; diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index d88daceb0d9..26c756ecd55 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -101,6 +101,13 @@ class TestCppcheck : public TestFixture { TEST_CASE(checkPlistOutput); TEST_CASE(premiumResultsCache); TEST_CASE(purgedConfiguration); + TEST_CASE(checkConfigurationInvalid); + TEST_CASE(checkConfigurationValid); + TEST_CASE(checkConfigurationSuppression); + TEST_CASE(checkConfigurationExplicitError); + TEST_CASE(checkConfigurationCombinedDefines); + TEST_CASE(checkConfigurationNormalAnalysis); + TEST_CASE(checkConfigurationHeader); TEST_CASE(recheckInclude); } @@ -126,6 +133,7 @@ class TestCppcheck : public TestFixture { bool foundTooManyConfigs = false; bool foundMissingInclude = false; // #11984 bool foundMissingIncludeSystem = false; // #11984 + bool foundInvalidConfiguration = false; for (const std::string & it : errorLogger.ids) { if (it == "purgedConfiguration") foundPurgedConfiguration = true; @@ -135,11 +143,14 @@ class TestCppcheck : public TestFixture { foundMissingInclude = true; else if (it == "missingIncludeSystem") foundMissingIncludeSystem = true; + else if (it == "invalidConfiguration") + foundInvalidConfiguration = true; } ASSERT(foundPurgedConfiguration); ASSERT(foundTooManyConfigs); ASSERT(foundMissingInclude); ASSERT(foundMissingIncludeSystem); + ASSERT(foundInvalidConfiguration); } static std::string exename_(const std::string& exe) @@ -639,6 +650,100 @@ class TestCppcheck : public TestFixture { it->toString(false, templateFormat, "")); } + static const char* invalidConfigurationCode() { + return "#ifndef PLATFORM\n" + "#error Select PLATFORM\n" + "#endif\n" + "int base;\n" + "#ifdef FEATURE\n" + "int feature;\n" + "#endif\n"; + } + + std::list configurationMessages(const char* code, const Settings& settings, const char* suppression = nullptr) const { + const ScopedFile source("check-config.c", code); + Settings configuredSettings = settings; + configuredSettings.templateFormat = templateFormat; + Suppressions supprs; + if (suppression) + ASSERT_EQUALS("", supprs.nomsg.addSuppressionLine(suppression)); + ErrorLogger2 errorLogger; + CppCheck cppcheck(configuredSettings, supprs, errorLogger, nullptr, true, {}); + cppcheck.check(FileWithDetails(source.path(), Path::identify(source.path(), false), 0)); + errorLogger.errmsgs.remove_if([](const ErrorMessage& msg) { + return msg.id == "logChecker"; + }); + return errorLogger.errmsgs; + } + + void checkConfigurationInvalid() const { + // Trac #6672: FEATURE is considered independently of the required PLATFORM. + const auto settings = dinit(Settings, $.checkConfiguration = true, $.templateFormat = templateFormat); + const auto messages = configurationMessages(invalidConfigurationCode(), settings); + ASSERT_EQUALS(1, messages.size()); + const ErrorMessage& msg = messages.front(); + ASSERT_EQUALS("invalidConfiguration", msg.id); + ASSERT(msg.severity == Severity::information); + ASSERT_EQUALS("check-config.c", msg.file0); + ASSERT_EQUALS(1, msg.callStack.size()); + ASSERT_EQUALS("check-config.c", msg.callStack.back().getfile(false)); + ASSERT_EQUALS(2, msg.callStack.back().line); + ASSERT(msg.shortMessage().find("FEATURE") != std::string::npos); + ASSERT(msg.shortMessage().find("#error Select PLATFORM") != std::string::npos); + } + + void checkConfigurationValid() const { + const auto settings = dinit(Settings, $.checkConfiguration = true, $.userDefines = "PLATFORM=1"); + ASSERT(configurationMessages(invalidConfigurationCode(), settings).empty()); + + const auto automaticSettings = dinit(Settings, $.checkConfiguration = true); + ASSERT(configurationMessages("#if 0\n#error inactive\n#endif\nint value;\n", automaticSettings).empty()); + ASSERT(configurationMessages("#ifdef FEATURE\nint feature;\n#endif\nint value;\n", automaticSettings).empty()); + } + + void checkConfigurationSuppression() const { + const auto settings = dinit(Settings, $.checkConfiguration = true); + ASSERT(configurationMessages(invalidConfigurationCode(), settings, "invalidConfiguration").empty()); + } + + void checkConfigurationExplicitError() const { + const auto settings = dinit(Settings, $.checkConfiguration = true, $.userDefines = "FEATURE=1"); + const auto messages = configurationMessages(invalidConfigurationCode(), settings); + ASSERT_EQUALS(1, messages.size()); + ASSERT_EQUALS("preprocessorErrorDirective", messages.front().id); + ASSERT(messages.front().severity == Severity::error); + } + + void checkConfigurationCombinedDefines() const { + const auto settings = dinit(Settings, $.checkConfiguration = true, $.force = true, $.userDefines = "EXTRA=7"); + const auto messages = configurationMessages(invalidConfigurationCode(), settings); + ASSERT_EQUALS(1, messages.size()); + ASSERT_EQUALS("invalidConfiguration", messages.front().id); + const std::string& message = messages.front().shortMessage(); + ASSERT(message.find("EXTRA=7;FEATURE") != std::string::npos); + ASSERT(message.find("EXTRA=7", message.find("EXTRA=7") + 1) == std::string::npos); + } + + void checkConfigurationNormalAnalysis() const { + const auto settings = dinit(Settings, $.force = true, $.severity.enable(Severity::information)); + ASSERT(configurationMessages(invalidConfigurationCode(), settings).empty()); + } + + void checkConfigurationHeader() const { + const ScopedFile header("config-error.h", "#ifndef PLATFORM\n#error Select PLATFORM\n#endif\n"); + const auto settings = dinit(Settings, $.checkConfiguration = true); + const auto messages = configurationMessages("#include \"config-error.h\"\n#ifdef FEATURE\nint feature;\n#endif\n", settings); + // Both the empty and FEATURE configurations fail in the included header. + ASSERT_EQUALS(2, messages.size()); + ASSERT(messages.front().shortMessage() != messages.back().shortMessage()); + for (const ErrorMessage& message : messages) { + ASSERT_EQUALS("invalidConfiguration", message.id); + ASSERT_EQUALS("check-config.c", message.file0); + ASSERT_EQUALS("config-error.h", message.callStack.back().getfile(false)); + ASSERT_EQUALS(2, message.callStack.back().line); + } + } + void recheckInclude() const { const auto settings = dinit(Settings,