Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions lib/cppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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<ErrorMessage::FileLocation> 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<std::string> 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)
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions lib/cppcheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
105 changes: 105 additions & 0 deletions test/testcppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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<ErrorMessage> 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,
Expand Down