diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index 9528360906e..5f13546c9e9 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -912,6 +912,10 @@ simplecpp::TokenList Preprocessor::preprocess(const std::string &cfg, std::vecto tokens2.removeComments(); + // ensure that guessed define macros without value are not used in the code + if (!validateCfg(cfg, macroUsage)) + return simplecpp::TokenList(files); + return tokens2; } @@ -1059,6 +1063,44 @@ void Preprocessor::invalidSuppression(const simplecpp::Location& loc, const std: error(loc, msg, "invalidSuppression"); } +bool Preprocessor::validateCfg(const std::string &cfg, const std::list ¯oUsageList) +{ + bool ret = true; + std::list defines; + splitcfg(cfg, defines, emptyString); + for (const std::string &define : defines) { + if (define.find('=') != std::string::npos) + continue; + const std::string macroName(define.substr(0, define.find('('))); + for (const simplecpp::MacroUsage &mu : macroUsageList) { + if (mu.macroValueKnown) + continue; + if (mu.macroName != macroName) + continue; + const bool directiveLocation = std::any_of(mDirectives.cbegin(), mDirectives.cend(), + [=](const Directive &dir) { + return mu.useLocation.file() == dir.file && mu.useLocation.line == dir.linenr; + }); + + if (!directiveLocation) { + if (mSettings.severity.isEnabled(Severity::information)) + validateCfgError(mu.useLocation.file(), mu.useLocation.line, cfg, macroName); + ret = false; + } + } + } + + return ret; +} + +void Preprocessor::validateCfgError(const std::string &file, const unsigned int line, const std::string &cfg, const std::string ¯o) +{ + const std::string id = "ConfigurationNotChecked"; + ErrorMessage::FileLocation loc(file, line, 0); + const ErrorMessage errmsg({std::move(loc)}, mFile0, Severity::information, "Skipping configuration '" + cfg + "' since the value of '" + macro + "' is unknown. Use -D if you want to check it. You can use -U to skip it explicitly.", id, Certainty::normal); + mErrorLogger->reportInfo(errmsg); +} + void Preprocessor::getErrorMessages(ErrorLogger &errorLogger, const Settings &settings) { std::vector files; @@ -1069,6 +1111,7 @@ void Preprocessor::getErrorMessages(ErrorLogger &errorLogger, const Settings &se loc.col = 2; preprocessor.missingInclude(loc, "", UserHeader); preprocessor.missingInclude(loc, "", SystemHeader); + preprocessor.validateCfgError("", 1, "X", "X"); preprocessor.error(loc, "message", simplecpp::Output::ERROR); preprocessor.error(loc, "message", simplecpp::Output::SYNTAX_ERROR); preprocessor.error(loc, "message", simplecpp::Output::UNHANDLED_CHAR_ERROR); diff --git a/lib/preprocessor.h b/lib/preprocessor.h index a211341691f..ab91ec172a0 100644 --- a/lib/preprocessor.h +++ b/lib/preprocessor.h @@ -122,6 +122,15 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { std::string getcode(const std::string &cfg, std::vector &files, bool writeLocations); + /** + * make sure empty configuration macros are not used in code. the given code must be a single configuration + * @param cfg configuration + * @param macroUsageList macro usage list + * @return true => configuration is valid + */ + bool validateCfg(const std::string &cfg, const std::list ¯oUsageList); + void validateCfgError(const std::string &file, const unsigned int line, const std::string &cfg, const std::string ¯o); + /** * Calculate HASH. Using toolinfo, tokens1, filedata. * diff --git a/test/testpreprocessor.cpp b/test/testpreprocessor.cpp index d072ebbf2e3..ce3459bd222 100644 --- a/test/testpreprocessor.cpp +++ b/test/testpreprocessor.cpp @@ -340,6 +340,9 @@ class TestPreprocessor : public TestFixture { TEST_CASE(getConfigsU6); TEST_CASE(getConfigsU7); + TEST_CASE(validateCfg1); + TEST_CASE(validateCfg2); + TEST_CASE(getConfigsAndCodeIssue14317); TEST_CASE(getConfigsMostGeneralConfigIssue14317); @@ -2668,6 +2671,36 @@ class TestPreprocessor : public TestFixture { ASSERT_EQUALS("\nY\n", getConfigsStr(code, "-DX")); } + void validateCfg1() { + Preprocessor preprocessor(settings0, this); + + std::vector files(1, "test.c"); + simplecpp::MacroUsage macroUsage(files, false); + macroUsage.useLocation.fileIndex = 0; + macroUsage.useLocation.line = 1; + macroUsage.macroName = "X"; + std::list macroUsageList(1, macroUsage); + + ASSERT_EQUALS(true, preprocessor.validateCfg("", macroUsageList)); + ASSERT_EQUALS(false, preprocessor.validateCfg("X",macroUsageList)); + ASSERT_EQUALS(false, preprocessor.validateCfg("A=42;X", macroUsageList)); + ASSERT_EQUALS(true, preprocessor.validateCfg("X=1", macroUsageList)); + ASSERT_EQUALS(true, preprocessor.validateCfg("Y", macroUsageList)); + + macroUsageList.front().macroValueKnown = true; // #8404 + ASSERT_EQUALS(true, preprocessor.validateCfg("X", macroUsageList)); + } + + void validateCfg2() { + const char filedata[] = "#ifdef ABC\n" + "#endif\n" + "int i = ABC;"; + + std::map actual; + preprocess(filedata, actual, "file.cpp"); + ASSERT_EQUALS("[file.cpp:3]: (information) Skipping configuration 'ABC' since the value of 'ABC' is unknown. Use -D if you want to check it. You can use -U to skip it explicitly.\n", errout.str()); + } + void getConfigsAndCodeIssue14317() { const char filedata[] = "bool test() {\n" "return\n"