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)) {
Comment on lines +1114 to +1115

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an AI review. Take it with a grain of salt and feel free to reject it by resolving the comment.

The behavior looks right to me. I tried the test case from the PR via the CLI: --check-config now reports invalidConfiguration for FEATURE=FEATURE, -DFEATURE without --force still gives only the existing preprocessorErrorDirective, and normal analysis is unchanged.

One maintainability concern: (mSettings.userDefines.empty() || mSettings.force) is the negation of showerror in Preprocessor::handleErrors(), and startsWith(output->msg, "#error") repeats the check in Preprocessor::reportOutput(). If one of those is changed later, this will silently start producing duplicates or missing messages. Maybe let the preprocessor tell whether it already reported the #error (for example, a small helper that returns showerror, or a flag/out-parameter from handleErrors()) instead of re-deriving the condition here?

Minor: the location building in invalidConfigurationMessage() (fromNativeSeparators + relativePaths) duplicates Preprocessor::error().

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