/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2026 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 "errorlogger.h"
#include "color.h"
#include "cppcheck.h"
#include "path.h"
#include "settings.h"
#include "suppressions.h"
#include "symboldatabase.h"
#include "token.h"
#include "tokenlist.h"
#include "utils.h"
#include "checkers.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "xml.h"
const std::set ErrorLogger::mCriticalErrorIds{
"cppcheckError",
"cppcheckLimit",
"includeNestedTooDeeply",
"internalAstError",
"instantiationError",
"internalError",
"missingFile",
"premium-internalError",
"premium-invalidArgument",
"premium-invalidLicense",
"preprocessorErrorDirective",
"syntaxError",
"unhandledChar",
"unknownMacro"
};
ErrorMessage::ErrorMessage()
: severity(Severity::none), cwe(0U), certainty(Certainty::normal)
{}
// TODO: id and msg are swapped compared to other calls
ErrorMessage::ErrorMessage(std::list callStack, std::string file1, Severity severity, const std::string &msg, std::string id, Certainty certainty) :
callStack(std::move(callStack)), // locations for this error message
id(std::move(id)), // set the message id
file0(std::move(file1)),
severity(severity), // severity for this error message
cwe(0U),
certainty(certainty)
{
// set the summary and verbose messages
setmsg(msg);
}
// TODO: id and msg are swapped compared to other calls
ErrorMessage::ErrorMessage(std::list callStack, std::string file1, Severity severity, const std::string &msg, std::string id, const CWE &cwe, Certainty certainty) :
callStack(std::move(callStack)), // locations for this error message
id(std::move(id)), // set the message id
file0(std::move(file1)),
severity(severity), // severity for this error message
cwe(cwe.id),
certainty(certainty)
{
// set the summary and verbose messages
setmsg(msg);
}
ErrorMessage::ErrorMessage(const std::list& callstack, const TokenList* list, Severity severity, std::string id, const std::string& msg, Certainty certainty)
: id(std::move(id)), severity(severity), cwe(0U), certainty(certainty)
{
// Format callstack
for (auto it = callstack.cbegin(); it != callstack.cend(); ++it) {
// --errorlist can provide null values here
if (!(*it))
continue;
callStack.emplace_back(*it, list);
}
if (list && !list->getFiles().empty())
file0 = list->getFiles()[0];
setmsg(msg);
calculateWarningHash(callstack);
}
ErrorMessage::ErrorMessage(const std::list& callstack, const TokenList* list, Severity severity, std::string id, const std::string& msg, const CWE &cwe, Certainty certainty)
: id(std::move(id)), severity(severity), cwe(cwe.id), certainty(certainty)
{
// Format callstack
for (const Token *tok: callstack) {
// --errorlist can provide null values here
if (!tok)
continue;
callStack.emplace_back(tok, list);
}
if (list && !list->getFiles().empty())
file0 = list->getFiles()[0];
setmsg(msg);
calculateWarningHash(callstack);
}
ErrorMessage::ErrorMessage(ErrorPath errorPath, const TokenList *tokenList, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty)
: id(id), severity(severity), cwe(cwe.id), certainty(certainty)
{
// Format callstack
for (ErrorPathItem& e: errorPath) {
const Token *tok = e.first;
// --errorlist can provide null values here
if (!tok)
continue;
std::string& path_info = e.second;
std::string info;
if (startsWith(path_info,"$symbol:") && path_info.find('\n') < path_info.size()) {
const std::string::size_type pos = path_info.find('\n');
const std::string symbolName = path_info.substr(8, pos - 8);
info = replaceStr(path_info.substr(pos+1), "$symbol", symbolName);
}
else {
info = std::move(path_info);
}
callStack.emplace_back(tok, std::move(info), tokenList);
}
if (tokenList && !tokenList->getFiles().empty())
file0 = tokenList->getFiles()[0];
setmsg(msg);
std::list tokens;
std::transform(errorPath.cbegin(), errorPath.cend(), std::back_inserter(tokens),
[](const ErrorPathItem& e) {
return e.first;
});
calculateWarningHash(tokens);
}
// TODO: improve errorhandling?
ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg)
: severity(Severity::none),
cwe(0U),
certainty(Certainty::normal)
{
const char * const unknown = "";
const char *attr = errmsg->Attribute("id");
id = attr ? attr : unknown;
attr = errmsg->Attribute("file0");
file0 = attr ? attr : "";
attr = errmsg->Attribute("severity");
severity = attr ? severityFromString(attr) : Severity::none;
attr = errmsg->Attribute("cwe");
// cppcheck-suppress templateInstantiation - TODO: fix this - see #11631
cwe.id = attr ? strToInt(attr) : 0;
attr = errmsg->Attribute("inconclusive");
certainty = (attr && (std::strcmp(attr, "true") == 0)) ? Certainty::inconclusive : Certainty::normal;
attr = errmsg->Attribute("msg");
mShortMessage = attr ? attr : "";
attr = errmsg->Attribute("verbose");
mVerboseMessage = attr ? attr : "";
attr = errmsg->Attribute("hash");
hash = attr ? strToInt(attr) : 0;
for (const tinyxml2::XMLElement *e = errmsg->FirstChildElement(); e; e = e->NextSiblingElement()) {
const char* name = e->Name();
if (std::strcmp(name,"location")==0) {
const char *strorigfile = e->Attribute("origfile");
const char *strfile = e->Attribute("file");
const char *strinfo = e->Attribute("info");
const char *strline = e->Attribute("line");
const char *strcolumn = e->Attribute("column");
const char *file = strfile ? strfile : unknown;
const char *origfile = strorigfile ? strorigfile : file;
const char *info = strinfo ? strinfo : "";
const int line = strline ? strToInt(strline) : 0;
const int column = strcolumn ? strToInt(strcolumn) : 0;
callStack.emplace_front(origfile, info, line, column);
if (strorigfile)
callStack.front().setfile(file);
} else if (std::strcmp(name,"symbol")==0) {
mSymbolNames += e->GetText();
}
}
}
void ErrorMessage::setmsg(const std::string &msg)
{
// If a message ends to a '\n' and contains only a one '\n'
// it will cause the mVerboseMessage to be empty which will show
// as an empty message to the user if --verbose is used.
// Even this doesn't cause problems with messages that have multiple
// lines, none of the error messages should end into it.
assert(!endsWith(msg,'\n'));
// The summary and verbose message are separated by a newline
// If there is no newline then both the summary and verbose messages
// are the given message
const std::string::size_type pos = msg.find('\n');
const std::string symbolName = mSymbolNames.empty() ? std::string() : mSymbolNames.substr(0, mSymbolNames.find('\n'));
if (pos == std::string::npos) {
mShortMessage = replaceStr(msg, "$symbol", symbolName);
mVerboseMessage = replaceStr(msg, "$symbol", symbolName);
} else if (startsWith(msg,"$symbol:")) {
mSymbolNames += msg.substr(8, pos-7);
setmsg(msg.substr(pos + 1));
} else {
mShortMessage = replaceStr(msg.substr(0, pos), "$symbol", symbolName);
mVerboseMessage = replaceStr(msg.substr(pos + 1), "$symbol", symbolName);
}
}
void ErrorMessage::calculateWarningHash(const std::list& callstack)
{
if (callstack.empty())
return;
// Calculate a hash for this warning message
std::string hashString;
for (const Token* tok: callstack) {
if (!tok)
continue;
if (!tok->scope())
return; // might be a syntax error before scope info has been set
if (tok->scope()->isExecutable()) {
// Executable scope => include all tokens in the function => if the
// function is changed the hash is changed
for (const Token* t = tok; t; t = t->previous()) {
if (!t->scope()->isExecutable())
break;
hashString += " " + t->str();
}
for (const Token* t = tok->next(); t; t = t->next()) {
if (!t->scope()->isExecutable())
break;
hashString += " " + t->str();
}
} else {
// Non executable scope => include tokens in current statement => if the current statement is changed the hash is changed
for (const Token* t = tok; t; t = t->previous()) {
if (t->str() == ";")
break;
if (t->scope() != tok->scope()) // stop on {} unless its an initializer
break;
hashString += " " + t->str();
}
for (const Token* t = tok->next(); t; t = t->next()) {
hashString += " " + t->str();
if (t->str() == ";")
break;
if (t->scope() != tok->scope()) // stop on {} unless its an initializer
break;
}
}
}
hashString = id + '\n' + mShortMessage + '\n' + hashString;
// hash algorithm: sdbm
// any hash algorithm can be used but it has to be the same hash on different platforms and compilers
hash = std::accumulate(hashString.cbegin(), hashString.cend(), std::size_t{0}, [](std::size_t h, unsigned char c) {
return static_cast(c) + (h getSourceFilePath(), 0, 0);
}
}
ErrorMessage errmsg(std::move(locationList),
tokenList ? tokenList->getSourceFilePath() : filename,
Severity::error,
(msg.empty() ? "" : (msg + ": ")) + internalError.errorMessage,
internalError.id,
Certainty::normal);
// TODO: find a better way
if (!internalError.details.empty())
errmsg.mVerboseMessage = errmsg.mVerboseMessage + ": " + internalError.details;
return errmsg;
}
std::string ErrorMessage::serialize() const
{
// Serialize this message into a simple string
std::string oss;
serializeString(oss, id);
serializeString(oss, severityToString(severity));
serializeString(oss, std::to_string(cwe.id));
serializeString(oss, std::to_string(hash));
serializeString(oss, fixInvalidChars(remark));
serializeString(oss, file0);
serializeString(oss, (certainty == Certainty::inconclusive) ? "1" : "0");
const std::string saneShortMessage = fixInvalidChars(mShortMessage);
const std::string saneVerboseMessage = fixInvalidChars(mVerboseMessage);
serializeString(oss, saneShortMessage);
serializeString(oss, saneVerboseMessage);
serializeString(oss, mSymbolNames);
oss += std::to_string(callStack.size());
oss += " ";
for (auto loc = callStack.cbegin(); loc != callStack.cend(); ++loc) {
std::string frame;
frame += std::to_string(loc->line);
frame += '\t';
frame += std::to_string(loc->column);
frame += '\t';
frame += loc->getfile(false);
frame += '\t';
frame += loc->getOrigFile(false);
frame += '\t';
frame += loc->getinfo();
serializeString(oss, frame);
}
return oss;
}
void ErrorMessage::deserialize(const std::string &data)
{
// TODO: clear all fields
certainty = Certainty::normal;
callStack.clear();
std::istringstream iss(data);
std::array results;
std::size_t elem = 0;
while (iss.good() && elem < 10) {
unsigned int len = 0;
if (!(iss >> len))
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid length");
if (iss.get() != ' ')
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid separator");
if (!iss.good())
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data");
std::string temp;
if (len > 0) {
temp.resize(len);
iss.read(&temp[0], len);
if (!iss.good())
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data");
}
results[elem++] = std::move(temp);
}
if (!iss.good())
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data");
if (elem != 10)
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - insufficient elements");
id = std::move(results[0]);
severity = severityFromString(results[1]);
cwe.id = 0;
if (!results[2].empty()) {
std::string err;
if (!strToInt(results[2], cwe.id, &err))
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid CWE ID - " + err);
}
hash = 0;
if (!results[3].empty()) {
std::string err;
if (!strToInt(results[3], hash, &err))
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid hash - " + err);
}
remark = std::move(results[4]);
file0 = std::move(results[5]);
if (results[6] == "1")
certainty = Certainty::inconclusive;
mShortMessage = std::move(results[7]);
mVerboseMessage = std::move(results[8]);
mSymbolNames = std::move(results[9]);
unsigned int stackSize = 0;
if (!(iss >> stackSize))
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid stack size");
if (iss.get() != ' ')
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid separator");
if (stackSize == 0)
return;
while (iss.good()) {
unsigned int len = 0;
if (!(iss >> len))
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid length (stack)");
if (iss.get() != ' ')
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid separator (stack)");
std::string temp;
if (len > 0) {
temp.resize(len);
iss.read(&temp[0], len);
if (!iss.good())
throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data (stack)");
}
std::vector substrings;
substrings.reserve(5);
for (std::string::size_type pos = 0; pos < temp.size() && substrings.size() < 5; ++pos) {
if (substrings.size() == 4) {
substrings.push_back(temp.substr(pos));
break;
}
const std::string::size_type start = pos;
pos = temp.find('\t', pos);
if (pos == std::string::npos) {
substrings.push_back(temp.substr(start));
break;
}
substrings.push_back(temp.substr(start, pos - start));
}
if (substrings.size() < 4)
throw InternalError(nullptr, "Internal Error: Deserializing of error message failed");
// (*loc).line \r\n"
= s.size())
return s.substr(0,pos1) + to;
if (s[pos2] == '_' || std::isalnum(s[pos2])) {
pos1++;
continue;
}
s.replace(pos1, from.size(), to);
pos1 += to.size();
}
return s;
}
void substituteTemplateFormatStatic(std::string& templateFormat, bool eraseColors)
{
replaceSpecialChars(templateFormat);
replaceColors(templateFormat, eraseColors);
}
void substituteTemplateLocationStatic(std::string& templateLocation, bool eraseColors)
{
replaceSpecialChars(templateLocation);
replaceColors(templateLocation, eraseColors);
}
std::string getClassification(const std::string &guideline, ReportType reportType) {
if (guideline.empty())
return "";
const auto getClassification = [](const std::vector &info, const std::string &guideline) -> std::string {
const auto it = std::find_if(info.cbegin(), info.cend(), [&](const checkers::Info &i) {
return caseInsensitiveStringCompare(i.guideline, guideline) == 0;
});
if (it == info.cend())
return "";
return it->classification;
};
switch (reportType) {
case ReportType::autosar:
return getClassification(checkers::autosarInfo, guideline);
case ReportType::certC:
return getClassification(checkers::certCInfo, guideline);
case ReportType::certCpp:
return getClassification(checkers::certCppInfo, guideline);
case ReportType::misraC2012:
case ReportType::misraC2023:
case ReportType::misraC2025:
{
const bool isDirective = guideline.rfind("Dir ", 0) == 0;
const std::size_t offset = isDirective ? 4 : 0;
auto components = splitString(guideline.substr(offset), '.');
if (components.size() != 2)
return "";
const int a = std::stoi(components[0]);
const int b = std::stoi(components[1]);
const std::vector *info = nullptr;
switch (reportType) {
case ReportType::misraC2012:
info = isDirective ? &checkers::misraC2012Directives : &checkers::misraC2012Rules;
break;
case ReportType::misraC2023:
info = isDirective ? &checkers::misraC2023Directives : &checkers::misraC2023Rules;
break;
case ReportType::misraC2025:
info = isDirective ? &checkers::misraC2025Directives : &checkers::misraC2025Rules;
break;
default:
cppcheck::unreachable();
}
const auto it = std::find_if(info->cbegin(), info->cend(), [&](const checkers::MisraInfo &i) {
return i.a == a && i.b == b;
});
return it == info->cend() ? "" : it->str;
}
case ReportType::misraCpp2008:
case ReportType::misraCpp2023:
{
const std::vector *info;
std::vector components;
if (reportType == ReportType::misraCpp2008) {
info = &checkers::misraCpp2008Rules;
components = splitString(guideline, '-');
} else {
if (guideline.rfind("Dir ", 0) == 0) {
components = splitString(guideline.substr(4), '.');
info = &checkers::misraCpp2023Directives;
} else {
components = splitString(guideline, '.');
info = &checkers::misraCpp2023Rules;
}
}
if (components.size() != 3)
return "";
const int a = std::stoi(components[0]);
const int b = std::stoi(components[1]);
const int c = std::stoi(components[2]);
const auto it = std::find_if(info->cbegin(), info->cend(), [&](const checkers::MisraCppInfo &i) {
return i.a == a && i.b == b && i.c == c;
});
if (it == info->cend())
return "";
return it->classification;
}
default:
return "";
}
}
std::string getGuideline(const std::string &errId, ReportType reportType,
const std::map &guidelineMapping,
Severity severity)
{
std::string guideline;
switch (reportType) {
case ReportType::autosar:
if (errId.rfind("premium-autosar-", 0) == 0) {
guideline = errId.substr(16);
break;
}
if (errId.rfind("premium-misra-cpp-2008-", 0) == 0)
guideline = "M" + errId.substr(23);
break;
case ReportType::certC:
case ReportType::certCpp:
if (errId.rfind("premium-cert-", 0) == 0) {
guideline = errId.substr(13);
std::transform(guideline.begin(), guideline.end(),
guideline.begin(), static_cast(std::toupper));
}
break;
case ReportType::misraC2012:
case ReportType::misraC2023:
case ReportType::misraC2025:
if (errId.rfind("misra-c20", 0) == 0 || errId.rfind("premium-misra-c-20", 0) == 0) {
auto pos1 = errId.find("20") + 5;
if (pos1 >= errId.size())
break;
if (errId.compare(pos1,4,"dir-",0,4) == 0)
pos1 += 4;
const auto endpos = errId.find('-', pos1);
guideline = errId.substr(pos1, endpos-pos1);
}
break;
case ReportType::misraCpp2008:
if (errId.rfind("premium-misra-cpp-2008", 0) == 0)
guideline = errId.substr(23);
break;
case ReportType::misraCpp2023:
if (errId.rfind("premium-misra-cpp-2023", 0) == 0)
guideline = errId.substr(errId.rfind('-') + 1);
break;
default:
break;
}
if (!guideline.empty()) {
if (errId.find("-dir-") != std::string::npos)
guideline = "Dir " + guideline;
return guideline;
}
auto it = guidelineMapping.find(errId);
if (it != guidelineMapping.cend())
return it->second;
if (severity == Severity::error || severity == Severity::warning) {
it = guidelineMapping.find("error");
if (it != guidelineMapping.cend())
return it->second;
}
return "";
}
std::map createGuidelineMapping(ReportType reportType) {
std::map guidelineMapping;
const std::vector *idMapping1 = nullptr;
const std::vector *idMapping2 = nullptr;
std::string ext1, ext2;
switch (reportType) {
case ReportType::autosar:
idMapping1 = &checkers::idMappingAutosar;
break;
case ReportType::certCpp:
idMapping2 = &checkers::idMappingCertCpp;
ext2 = "-CPP";
FALLTHROUGH;
case ReportType::certC:
idMapping1 = &checkers::idMappingCertC;
ext1 = "-C";
break;
case ReportType::misraC2012:
case ReportType::misraC2023:
case ReportType::misraC2025:
idMapping1 = &checkers::idMappingMisraC;
break;
case ReportType::misraCpp2008:
idMapping1 = &checkers::idMappingMisraCpp2008;
break;
case ReportType::misraCpp2023:
idMapping1 = &checkers::idMappingMisraCpp2023;
break;
default:
break;
}
if (idMapping1) {
for (const auto &i : *idMapping1)
for (const std::string &cppcheckId : splitString(i.cppcheckId, ','))
guidelineMapping[cppcheckId] = i.guideline + ext1;
}
if (idMapping2) {
for (const auto &i : *idMapping2)
for (const std::string &cppcheckId : splitString(i.cppcheckId, ','))
guidelineMapping[cppcheckId] = i.guideline + ext2;
}
return guidelineMapping;
}