/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2025 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 "addoninfo.h"
#include "color.h"
#include "cppcheck.h"
#include "errorlogger.h"
#include "errortypes.h"
#include "filesettings.h"
#include "fixture.h"
#include "helpers.h"
#include "path.h"
#include "preprocessor.h"
#include "redirect.h"
#include "settings.h"
#include "standards.h"
#include "suppressions.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
class TestCppcheck : public TestFixture {
public:
TestCppcheck() : TestFixture("TestCppcheck") {}
private:
const std::string templateFormat{"{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]"};
class ErrorLogger2 : public ErrorLogger {
public:
std::list ids;
std::list errmsgs;
private:
void reportOut(const std::string & /*outmsg*/, Color /*c*/ = Color::Reset) override {}
void reportErr(const ErrorMessage &msg) override {
ids.push_back(msg.id);
errmsgs.push_back(msg);
}
void reportMetric(const std::string &metric) override {
(void) metric;
}
};
void run() override {
TEST_CASE(getErrorMessages);
TEST_CASE(checkWithFile);
TEST_CASE(checkWithFileWithTools);
TEST_CASE(checkWithFileWithToolsNoCommand);
TEST_CASE(checkWithFS);
TEST_CASE(checkWithFSWithTools);
TEST_CASE(checkWithFSWithToolsNoCommand);
TEST_CASE(suppress_error_library);
TEST_CASE(unique_errors);
TEST_CASE(unique_errors_2);
TEST_CASE(isPremiumCodingStandardId);
TEST_CASE(getDumpFileContentsRawTokens);
TEST_CASE(getDumpFileContentsLibrary);
TEST_CASE(checkPlistOutput);
TEST_CASE(premiumResultsCache);
TEST_CASE(purgedConfiguration);
}
void getErrorMessages() const {
ErrorLogger2 errorLogger;
CppCheck::getErrorMessages(errorLogger);
ASSERT(!errorLogger.ids.empty());
// Check if there are duplicate error ids in errorLogger.id
std::string duplicate;
for (auto it = errorLogger.ids.cbegin();
it != errorLogger.ids.cend();
++it) {
if (std::find(errorLogger.ids.cbegin(), it, *it) != it) {
duplicate = "Duplicate ID: " + *it;
break;
}
}
ASSERT_EQUALS("", duplicate);
// Check for error ids from this class.
bool foundPurgedConfiguration = false;
bool foundTooManyConfigs = false;
bool foundMissingInclude = false; // #11984
bool foundMissingIncludeSystem = false; // #11984
for (const std::string & it : errorLogger.ids) {
if (it == "purgedConfiguration")
foundPurgedConfiguration = true;
else if (it == "toomanyconfigs")
foundTooManyConfigs = true;
else if (it == "missingInclude")
foundMissingInclude = true;
else if (it == "missingIncludeSystem")
foundMissingIncludeSystem = true;
}
ASSERT(foundPurgedConfiguration);
ASSERT(foundTooManyConfigs);
ASSERT(foundMissingInclude);
ASSERT(foundMissingIncludeSystem);
}
static std::string exename_(const std::string& exe)
{
#ifdef _WIN32
return exe + ".exe";
#else
return exe;
#endif
}
CppCheck::ExecuteCmdFn getExecuteCommand(const std::string& fname, int& called) const
{
// cppcheck-suppress passedByValue - used as callback so we need to preserve the signature
// NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature
return [&](std::string exe, std::vector args, std::string redirect, std::string& /*output*/) -> int {
++called;
if (exe == exename_("clang-tidy"))
{
ASSERT_EQUALS(4, args.size());
ASSERT_EQUALS("-quiet", args[0]);
ASSERT_EQUALS("-checks=*,-clang-analyzer-*,-llvm*", args[1]);
ASSERT_EQUALS(fname, args[2]);
ASSERT_EQUALS("--", args[3]);
ASSERT_EQUALS("2>&1", redirect);
return EXIT_SUCCESS;
}
if (exe == exename_("python3"))
{
ASSERT_EQUALS(1, args.size());
ASSERT_EQUALS("--version", args[0]);
ASSERT_EQUALS("2>&1", redirect);
return EXIT_SUCCESS;
}
if (exe == exename_("python"))
{
ASSERT_EQUALS(1, args.size());
ASSERT_EQUALS("--version", args[0]);
ASSERT_EQUALS("2>&1", redirect);
return EXIT_SUCCESS;
}
ASSERT_MSG(false, "unhandled exe: " + exe);
return EXIT_FAILURE;
};
}
void checkWithFileInternal(const std::string& fname, bool tools, bool nocmd = false) const
{
REDIRECT;
ScopedFile file(fname,
"void f()\n"
"{\n"
" (void)(*((int*)0));\n"
"}");
int called = 0;
std::unordered_set addons;
std::vector addonInfo;
if (tools)
{
addons.emplace("testcppcheck");
addonInfo.emplace_back(/*AddonInfo()*/);
}
const auto s = dinit(Settings,
$.templateFormat = templateFormat,
$.clangTidy = tools,
$.addons = std::move (addons),
$.addonInfos = std::move (addonInfo));
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck::ExecuteCmdFn f;
if (tools && !nocmd) {
f = getExecuteCommand(fname, called);
}
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, f);
ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path(), Path::identify(file.path(), false), 0)));
// TODO: how to properly disable these warnings?
errorLogger.ids.erase(std::remove_if(errorLogger.ids.begin(), errorLogger.ids.end(), [](const std::string& id) {
return id == "logChecker";
}), errorLogger.ids.end());
errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) {
return msg.id == "logChecker";
}), errorLogger.errmsgs.end());
if (tools)
{
ASSERT_EQUALS(2, errorLogger.ids.size());
auto it = errorLogger.errmsgs.cbegin();
ASSERT_EQUALS("nullPointer", it->id);
++it;
if (nocmd)
{
ASSERT_EQUALS("internalError", it->id);
ASSERT_EQUALS("Bailing out from analysis: Checking file failed: Failed to execute addon - no command callback provided", it->shortMessage()); // TODO: add addon name
// TODO: clang-tidy is currently not invoked for file inputs - see #12053
// TODO: needs to become a proper error
TODO_ASSERT_EQUALS("Failed to execute '" + exename_("clang-tidy") + "' (no command callback provided)\n", "", GET_REDIRECT_ERROUT);
ASSERT_EQUALS(0, called); // not called because we check if the callback exists
}
else
{
ASSERT_EQUALS("internalError", it->id);
ASSERT_EQUALS("Bailing out from analysis: Checking file failed: Failed to auto detect python", it->shortMessage()); // TODO: clarify what python is used for
// TODO: we cannot check this because the python detection is cached globally so this result will different dependent on how the test is called
//ASSERT_EQUALS(2, called);
}
}
else
{
ASSERT_EQUALS(0, called);
ASSERT_EQUALS(1, errorLogger.ids.size());
ASSERT_EQUALS("nullPointer", *errorLogger.ids.cbegin());
}
}
void checkWithFile() const {
checkWithFileInternal("file.c", false);
}
void checkWithFileWithTools() const {
checkWithFileInternal("file_tools.c", true);
}
void checkWithFileWithToolsNoCommand() const {
checkWithFileInternal("file_tools_nocmd.c", true, true);
}
void checkWithFSInternal(const std::string& fname, bool tools, bool nocmd = false) const
{
REDIRECT;
ScopedFile file(fname,
"void f()\n"
"{\n"
" (void)(*((int*)0));\n"
"}");
int called = 0;
std::unordered_set addons;
std::vector addonInfo;
if (tools)
{
addons.emplace("testcppcheck");
addonInfo.emplace_back(/*AddonInfo()*/);
}
const auto s = dinit(Settings,
$.templateFormat = templateFormat,
$.clangTidy = tools,
$.addons = std::move (addons),
$.addonInfos = std::move (addonInfo));
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck::ExecuteCmdFn f;
if (tools && !nocmd) {
f = getExecuteCommand(fname, called);
}
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, f);
FileSettings fs{file.path(), Path::identify(file.path(), false), 0};
ASSERT_EQUALS(1, cppcheck.check(fs));
// TODO: how to properly disable these warnings?
errorLogger.ids.erase(std::remove_if(errorLogger.ids.begin(), errorLogger.ids.end(), [](const std::string& id) {
return id == "logChecker";
}), errorLogger.ids.end());
errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) {
return msg.id == "logChecker";
}), errorLogger.errmsgs.end());
if (tools)
{
ASSERT_EQUALS(2, errorLogger.ids.size());
auto it = errorLogger.errmsgs.cbegin();
ASSERT_EQUALS("nullPointer", it->id);
++it;
if (nocmd)
{
ASSERT_EQUALS("internalError", it->id);
ASSERT_EQUALS("Bailing out from analysis: Checking file failed: Failed to execute addon - no command callback provided", it->shortMessage()); // TODO: add addon name
// TODO: needs to become a proper error
ASSERT_EQUALS("Failed to execute '" + exename_("clang-tidy") + "' (no command callback provided)\n", GET_REDIRECT_ERROUT);
ASSERT_EQUALS(0, called); // not called because we check if the callback exists
}
else
{
ASSERT_EQUALS("internalError", it->id);
ASSERT_EQUALS("Bailing out from analysis: Checking file failed: Failed to auto detect python", it->shortMessage()); // TODO: clarify what python is used for
// TODO: we cannot check this because the python detection is cached globally so this result will different dependent on how the test is called
//ASSERT_EQUALS(3, called);
}
}
else
{
ASSERT_EQUALS(0, called);
ASSERT_EQUALS(1, errorLogger.ids.size());
ASSERT_EQUALS("nullPointer", *errorLogger.ids.cbegin());
}
}
void checkWithFS() const {
checkWithFSInternal("fs.c", false);
}
void checkWithFSWithTools() const {
checkWithFSInternal("fs_tools.c", true);
}
void checkWithFSWithToolsNoCommand() const {
checkWithFSInternal("fs_tools_nocmd.c", true, true);
}
void suppress_error_library() const
{
ScopedFile file("suppr_err_lib.c",
"void f()\n"
"{\n"
" (void)(*((int*)0));\n"
"}");
const char xmldata[] = R"()";
const Settings s = settingsBuilder().libraryxml(xmldata).build();
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
ASSERT_EQUALS(0, cppcheck.check(FileWithDetails(file.path(), Path::identify(file.path(), false), 0)));
// TODO: how to properly disable these warnings?
errorLogger.ids.erase(std::remove_if(errorLogger.ids.begin(), errorLogger.ids.end(), [](const std::string& id) {
return id == "logChecker";
}), errorLogger.ids.end());
ASSERT_EQUALS(0, errorLogger.ids.size());
}
// TODO: how to actually get duplicated findings
void unique_errors() const
{
ScopedFile file("inc.h",
"inline void f()\n"
"{\n"
" (void)(*((int*)0));\n"
"}");
ScopedFile test_file_a("a.c",
"#include \"inc.h\"");
ScopedFile test_file_b("b.c",
"#include \"inc.h\"");
// this is the "simple" format
const auto s = dinit(Settings, $.templateFormat = templateFormat); // TODO: remove when we only longer rely on toString() in unique message handling
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(test_file_a.path(), Path::identify(test_file_a.path(), false), 0)));
ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(test_file_b.path(), Path::identify(test_file_b.path(), false), 0)));
// TODO: how to properly disable these warnings?
errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) {
return msg.id == "logChecker";
}), errorLogger.errmsgs.end());
// the internal errorlist is cleared after each check() call
ASSERT_EQUALS(2, errorLogger.errmsgs.size());
auto it = errorLogger.errmsgs.cbegin();
ASSERT_EQUALS("a.c", it->file0);
ASSERT_EQUALS("nullPointer", it->id);
++it;
ASSERT_EQUALS("b.c", it->file0);
ASSERT_EQUALS("nullPointer", it->id);
}
void unique_errors_2() const
{
ScopedFile test_file("c.c",
"void f()\n"
"{\n"
"const long m[9] = {};\n"
"long a=m[9], b=m[9];\n"
"(void)a;\n"
"(void)b;\n"
"}");
// this is the "simple" format
const auto s = dinit(Settings, $.templateFormat = templateFormat); // TODO: remove when we only longer rely on toString() in unique message handling?
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(test_file.path(), Path::identify(test_file.path(), false), 0)));
// TODO: how to properly disable these warnings?
errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) {
return msg.id == "logChecker";
}), errorLogger.errmsgs.end());
// the internal errorlist is cleared after each check() call
ASSERT_EQUALS(2, errorLogger.errmsgs.size());
auto it = errorLogger.errmsgs.cbegin();
ASSERT_EQUALS("c.c", it->file0);
ASSERT_EQUALS(1, it->callStack.size());
{
auto stack = it->callStack.cbegin();
ASSERT_EQUALS(4, stack->line);
ASSERT_EQUALS(9, stack->column);
}
ASSERT_EQUALS("arrayIndexOutOfBounds", it->id);
++it;
ASSERT_EQUALS("c.c", it->file0);
ASSERT_EQUALS(1, it->callStack.size());
{
auto stack = it->callStack.cbegin();
ASSERT_EQUALS(4, stack->line);
ASSERT_EQUALS(17, stack->column);
}
ASSERT_EQUALS("arrayIndexOutOfBounds", it->id);
}
void isPremiumCodingStandardId() const {
Suppressions supprs;
ErrorLogger2 errorLogger;
{
const auto s = dinit(Settings, $.premiumArgs = "");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("misra-c2012-0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("misra-c2023-0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-misra-c-2012-0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-misra-c-2023-0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-misra-c-2025-0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-misra-c-2025-dir-0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-misra-c++-2008-0-0-0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-misra-c++-2023-0.0.0"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-cert-int50-cpp"));
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("premium-autosar-0-0-0"));
}
{
const auto s = dinit(Settings, $.premiumArgs = "--misra-c-2012 --cert-c++-2016 --autosar");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
ASSERT_EQUALS(false, cppcheck.isPremiumCodingStandardId("misra-c2012-0.0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-misra-c-2012-0.0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-misra-c-2023-0.0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-misra-c-2025-0.0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-misra-c-2025-dir-0.0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-misra-c++-2008-0-0-0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-misra-c++-2023-0.0.0"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-cert-int50-cpp"));
ASSERT_EQUALS(true, cppcheck.isPremiumCodingStandardId("premium-autosar-0-0-0"));
}
}
void getDumpFileContentsRawTokens() const {
Settings s;
s.relativePaths = true;
s.basePaths.emplace_back("/some/path");
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
std::vector files{"/some/path/test.c"};
simplecpp::TokenList tokens1(files);
const std::string expected = " \n"
" \n"
" \n";
ASSERT_EQUALS(expected, cppcheck.getDumpFileContentsRawTokens(files, tokens1));
const char code[] = "//x \\ \n"
"y\n"
";\n";
simplecpp::OutputList outputList;
const simplecpp::TokenList tokens2(code, files, "", &outputList);
const std::string expected2 = " \n"
" \n"
" \n"
" \n"
" \n"
" \n";
ASSERT_EQUALS(expected2, cppcheck.getDumpFileContentsRawTokens(files, tokens2));
}
void getDumpFileContentsLibrary() const {
Suppressions supprs;
ErrorLogger2 errorLogger;
{
Settings s;
s.libraries.emplace_back("std.cfg");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
//std::vector files{ "/some/path/test.c" };
const std::string expected = " \n";
ASSERT_EQUALS(expected, cppcheck.getLibraryDumpData());
}
{
Settings s;
s.libraries.emplace_back("std.cfg");
s.libraries.emplace_back("posix.cfg");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
const std::string expected = " \n \n";
ASSERT_EQUALS(expected, cppcheck.getLibraryDumpData());
}
}
void checkPlistOutput() const {
Suppressions supprs;
ErrorLogger2 errorLogger;
std::vector files = {"textfile.txt"};
{
const auto s = dinit(Settings, $.templateFormat = templateFormat, $.plistOutput = "output");
const ScopedFile file("file", "");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
const FileWithDetails fileWithDetails {file.path(), Path::identify(file.path(), false), 0};
cppcheck.checkPlistOutput(fileWithDetails, files);
const std::string outputFile {"outputfile_" + std::to_string(std::hash {}(fileWithDetails.spath())) + ".plist"};
ASSERT(Path::exists(outputFile));
std::remove(outputFile.c_str());
}
{
const auto s = dinit(Settings, $.plistOutput = "output");
const ScopedFile file("file.c", "");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
const FileWithDetails fileWithDetails {file.path(), Path::identify(file.path(), false), 0};
cppcheck.checkPlistOutput(fileWithDetails, files);
const std::string outputFile {"outputfile_" + std::to_string(std::hash {}(fileWithDetails.spath())) + ".plist"};
ASSERT(Path::exists(outputFile));
std::remove(outputFile.c_str());
}
{
Settings s;
const ScopedFile file("file.c", "");
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
cppcheck.checkPlistOutput(FileWithDetails(file.path(), Path::identify(file.path(), false), 0), files);
}
}
void premiumResultsCache() const {
// Trac #13889 - cached misra results are shown after removing --premium=misra-c-2012 option
Settings settings;
Suppressions supprs;
ErrorLogger2 errorLogger;
std::vector files;
const char code[] = "void f();\nint x;\n";
simplecpp::TokenList tokens(code, files, "m1.c");
Preprocessor preprocessor(tokens, settings, errorLogger, Standards::Language::C);
ASSERT(preprocessor.loadFiles(files));
AddonInfo premiumaddon;
premiumaddon.name = "premiumaddon.json";
premiumaddon.executable = "premiumaddon";
settings.cppcheckCfgProductName = "Cppcheck Premium 0.0.0";
settings.addons.insert(premiumaddon.name);
settings.addonInfos.push_back(premiumaddon);
settings.premiumArgs = "misra-c-2012";
CppCheck check(settings, supprs, errorLogger, nullptr, false, {});
const size_t hash1 = check.calculateHash(preprocessor);
settings.premiumArgs = "";
const size_t hash2 = check.calculateHash(preprocessor);
// cppcheck-suppress knownConditionTrueFalse
ASSERT(hash1 != hash2);
}
void purgedConfiguration() const
{
ScopedFile test_file("test.cpp",
"#ifdef X\n"
"#endif\n"
"int main() {}\n");
// this is the "simple" format
const auto s = dinit(Settings,
$.templateFormat = templateFormat, // TODO: remove when we only longer rely on toString() in unique message handling
$.severity.enable (Severity::information);
$.debugwarnings = true);
Suppressions supprs;
ErrorLogger2 errorLogger;
CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {});
ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(test_file.path(), Path::identify(test_file.path(), false), 0)));
// TODO: how to properly disable these warnings?
errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) {
return msg.id == "logChecker";
}), errorLogger.errmsgs.end());
// the internal errorlist is cleared after each check() call
ASSERT_EQUALS(1, errorLogger.errmsgs.size());
auto it = errorLogger.errmsgs.cbegin();
ASSERT_EQUALS("test.cpp:0:0: information: The configuration 'X' was not checked because its code equals another one. [purgedConfiguration]",
it->toString(false, templateFormat, ""));
}
// TODO: test suppressions
// TODO: test all with FS
};
REGISTER_TEST(TestCppcheck)