/*
* 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 "token.h"
#include "tokenlist.h"
#include "utils.h"
#include "checkers.h"
#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);
}
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);
// hash = calculateWarningHash(list, hashWarning.str());
}
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);
// hash = calculateWarningHash(tokenList, hashWarning.str());
}
// 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);
}
}
static void serializeString(std::string &oss, const std::string & str)
{
oss += std::to_string(str.length());
oss += " ";
oss += str;
}
ErrorMessage ErrorMessage::fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename, const std::string& msg)
{
if (internalError.token)
assert(tokenList != nullptr); // we need to make sure we can look up the provided token
std::list locationList;
if (tokenList && internalError.token) {
locationList.emplace_back(internalError.token, tokenList);
} else {
locationList.emplace_back(filename, 0, 0);
if (tokenList && (filename != tokenList->getSourceFilePath())) {
locationList.emplace_back(tokenList->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 << '\t' << (*loc).column << '\t' << (*loc).getfile(false) << '\t' << loc->getOrigFile(false) << '\t' << loc->getinfo();
std::string info;
if (substrings.size() == 5)
info = std::move(substrings[4]);
ErrorMessage::FileLocation loc(substrings[3], std::move(info), strToInt(substrings[0]), strToInt(substrings[1]));
loc.setfile(std::move(substrings[2]));
callStack.push_back(std::move(loc));
if (callStack.size() >= stackSize)
break;
}
}
std::string ErrorMessage::getXMLHeader(std::string productName, int xmlVersion)
{
const auto nameAndVersion = Settings::getNameAndVersion(productName);
productName = nameAndVersion.first;
// TODO: lacks extra version
const std::string version = nameAndVersion.first.empty() ? CppCheck::version() : nameAndVersion.second;
tinyxml2::XMLPrinter printer;
// standard xml header
printer.PushDeclaration("xml version=\"1.0\" encoding=\"UTF-8\"");
// header
printer.OpenElement("results", false);
printer.PushAttribute("version", xmlVersion);
printer.OpenElement("cppcheck", false);
if (!productName.empty())
printer.PushAttribute("product-name", productName.c_str());
printer.PushAttribute("version", version.c_str());
printer.CloseElement(false);
printer.OpenElement("errors", false);
return std::string(printer.CStr()) + '>';
}
std::string ErrorMessage::getXMLFooter(int xmlVersion)
{
return xmlVersion == 3 ? "" : " \n";
}
// There is no utf-8 support around but the strings should at least be safe for to tinyxml2.
// See #5300 "Invalid encoding in XML output" and #6431 "Invalid XML created - Invalid encoding of string literal "
std::string ErrorMessage::fixInvalidChars(const std::string& raw)
{
std::string result;
result.reserve(raw.length());
auto from=raw.cbegin();
while (from!=raw.cend()) {
if (std::isprint(static_cast(*from))) {
result.push_back(*from);
} else {
std::ostringstream es;
// straight cast to (unsigned) doesn't work out.
const unsigned uFrom = static_cast(*from);
es << '\\' << std::setbase(8) << std::setw(3) << std::setfill('0') << uFrom;
result += es.str();
}
++from;
}
return result;
}
std::string ErrorMessage::toXML() const
{
tinyxml2::XMLPrinter printer(nullptr, false, 2);
printer.OpenElement("error", false);
printer.PushAttribute("id", id.c_str());
if (!guideline.empty())
printer.PushAttribute("guideline", guideline.c_str());
printer.PushAttribute("severity", severityToString(severity).c_str());
if (!classification.empty())
printer.PushAttribute("classification", classification.c_str());
printer.PushAttribute("msg", fixInvalidChars(mShortMessage).c_str());
printer.PushAttribute("verbose", fixInvalidChars(mVerboseMessage).c_str());
if (cwe.id)
printer.PushAttribute("cwe", cwe.id);
if (hash)
printer.PushAttribute("hash", std::to_string(hash).c_str());
if (certainty == Certainty::inconclusive)
printer.PushAttribute("inconclusive", "true");
if (!file0.empty())
printer.PushAttribute("file0", file0.c_str());
if (!remark.empty())
printer.PushAttribute("remark", fixInvalidChars(remark).c_str());
for (auto it = callStack.crbegin(); it != callStack.crend(); ++it) {
printer.OpenElement("location", false);
const std::string origfile = it->getOrigFile(false);
const std::string file = it->getfile(false);
if (origfile != file)
printer.PushAttribute("origfile", origfile.c_str());
printer.PushAttribute("file", file.c_str());
printer.PushAttribute("line", std::max(it->line,0));
printer.PushAttribute("column", it->column);
if (!it->getinfo().empty())
printer.PushAttribute("info", fixInvalidChars(it->getinfo()).c_str());
printer.CloseElement(false);
}
for (std::string::size_type pos = 0; pos < mSymbolNames.size();) {
const std::string::size_type pos2 = mSymbolNames.find('\n', pos);
std::string symbolName;
if (pos2 == std::string::npos) {
symbolName = mSymbolNames.substr(pos);
pos = pos2;
} else {
symbolName = mSymbolNames.substr(pos, pos2-pos);
pos = pos2 + 1;
}
printer.OpenElement("symbol", false);
printer.PushText(symbolName.c_str());
printer.CloseElement(false);
}
printer.CloseElement(false);
return printer.CStr();
}
// TODO: read info from some shared resource instead?
static std::string readCode(const std::string &file, int linenr, int column, const char endl[])
{
std::ifstream fin(file);
std::string line;
while (linenr > 0 && std::getline(fin,line)) {
linenr--;
}
const std::string::size_type endPos = line.find_last_not_of("\r\n\t ");
if (endPos + 1 < line.size())
line.erase(endPos + 1);
std::string::size_type pos = 0;
while ((pos = line.find('\t', pos)) != std::string::npos)
line[pos] = ' ';
return line + endl + std::string((column>0 ? column-1 : 0), ' ') + '^';
}
static void replaceSpecialChars(std::string& source)
{
// Support a few special characters to allow to specific formatting, see http://sourceforge.net/apps/phpbb/cppcheck/viewtopic.php?f=4&t=494&sid=21715d362c0dbafd3791da4d9522f814
// Substitution should be done first so messages from cppcheck never get translated.
static const std::unordered_map substitutionMap = {
{'b', "\b"},
{'n', "\n"},
{'r', "\r"},
{'t', "\t"}
};
std::string::size_type index = 0;
while ((index = source.find('\\', index)) != std::string::npos) {
const char searchFor = source[index+1];
const auto it = substitutionMap.find(searchFor);
if (it == substitutionMap.end()) {
index += 1;
continue;
}
const std::string& replaceWith = it->second;
source.replace(index, 2, replaceWith);
index += replaceWith.length();
}
}
static void replace(std::string& source, const std::unordered_map &substitutionMap)
{
std::string::size_type index = 0;
while ((index = source.find('{', index)) != std::string::npos) {
const std::string::size_type end = source.find('}', index);
if (end == std::string::npos)
break;
const std::string searchFor = source.substr(index, end-index+1);
const auto it = substitutionMap.find(searchFor);
if (it == substitutionMap.end()) {
index += 1;
continue;
}
const std::string& replaceWith = it->second;
source.replace(index, searchFor.length(), replaceWith);
index += replaceWith.length();
}
}
static void replaceColors(std::string& source, bool erase) {
// TODO: colors are not applied when either stdout or stderr is not a TTY because we resolve them before the stream usage
static const std::unordered_map substitutionMapReplace =
{
{"{reset}", ::toString(Color::Reset)},
{"{bold}", ::toString(Color::Bold)},
{"{dim}", ::toString(Color::Dim)},
{"{red}", ::toString(Color::FgRed)},
{"{green}", ::toString(Color::FgGreen)},
{"{blue}", ::toString(Color::FgBlue)},
{"{magenta}", ::toString(Color::FgMagenta)},
{"{default}", ::toString(Color::FgDefault)},
};
static const std::unordered_map substitutionMapErase =
{
{"{reset}", ""},
{"{bold}", ""},
{"{dim}", ""},
{"{red}", ""},
{"{green}", ""},
{"{blue}", ""},
{"{magenta}", ""},
{"{default}", ""},
};
if (!erase)
replace(source, substitutionMapReplace);
else
replace(source, substitutionMapErase);
}
std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation) const
{
assert(!templateFormat.empty());
// template is given. Reformat the output according to it
std::string result = templateFormat;
// replace id with guideline if present
// replace severity with classification if present
const std::string idStr = guideline.empty() ? id : guideline;
const std::string severityStr = classification.empty() ? severityToString(severity) : classification;
findAndReplace(result, "{id}", idStr);
std::string::size_type pos1 = result.find("{inconclusive:");
while (pos1 != std::string::npos) {
const std::string::size_type pos2 = result.find('}', pos1+1);
const std::string replaceFrom = result.substr(pos1,pos2-pos1+1);
const std::string replaceWith = (certainty == Certainty::inconclusive) ? result.substr(pos1+14, pos2-pos1-14) : std::string();
findAndReplace(result, replaceFrom, replaceWith);
pos1 = result.find("{inconclusive:", pos1);
}
findAndReplace(result, "{severity}", severityStr);
findAndReplace(result, "{cwe}", std::to_string(cwe.id));
findAndReplace(result, "{message}", verbose ? mVerboseMessage : mShortMessage);
findAndReplace(result, "{remark}", remark);
if (!callStack.empty()) {
if (result.find("{callstack}") != std::string::npos)
findAndReplace(result, "{callstack}", ErrorLogger::callStackToString(callStack));
findAndReplace(result, "{file}", callStack.back().getfile());
findAndReplace(result, "{line}", std::to_string(callStack.back().line));
findAndReplace(result, "{column}", std::to_string(callStack.back().column));
if (result.find("{code}") != std::string::npos) {
const std::string::size_type pos = result.find('\r');
const char *endl;
if (pos == std::string::npos)
endl = "\n";
else if (pos+1 < result.size() && result[pos+1] == '\n')
endl = "\r\n";
else
endl = "\r";
findAndReplace(result, "{code}", readCode(callStack.back().getOrigFile(), callStack.back().line, callStack.back().column, endl));
}
} else {
static const std::unordered_map callStackSubstitutionMap =
{
{"{callstack}", ""},
{"{file}", "nofile"},
{"{line}", "0"},
{"{column}", "0"},
{"{code}", ""}
};
replace(result, callStackSubstitutionMap);
}
if (!templateLocation.empty() && callStack.size() >= 2U) {
for (const FileLocation &fileLocation : callStack) {
std::string text = templateLocation;
findAndReplace(text, "{file}", fileLocation.getfile());
findAndReplace(text, "{line}", std::to_string(fileLocation.line));
findAndReplace(text, "{column}", std::to_string(fileLocation.column));
findAndReplace(text, "{info}", fileLocation.getinfo().empty() ? mShortMessage : fileLocation.getinfo());
if (text.find("{code}") != std::string::npos) {
const std::string::size_type pos = text.find('\r');
const char *endl;
if (pos == std::string::npos)
endl = "\n";
else if (pos+1 < text.size() && text[pos+1] == '\n')
endl = "\r\n";
else
endl = "\r";
findAndReplace(text, "{code}", readCode(fileLocation.getOrigFile(), fileLocation.line, fileLocation.column, endl));
}
result += '\n' + text;
}
}
return result;
}
std::string ErrorLogger::callStackToString(const std::list &callStack, bool addcolumn)
{
std::string str;
for (auto tok = callStack.cbegin(); tok != callStack.cend(); ++tok) {
str += (tok == callStack.cbegin() ? "" : " -> ");
str += tok->stringify(addcolumn);
}
return str;
}
ErrorMessage::FileLocation::FileLocation(const std::string &file, int line, unsigned int column)
: fileIndex(0), line(line), column(column), mOrigFileName(file), mFileName(Path::simplifyPath(file))
{}
ErrorMessage::FileLocation::FileLocation(const std::string &file, std::string info, int line, unsigned int column)
: fileIndex(0), line(line), column(column), mOrigFileName(file), mFileName(Path::simplifyPath(file)), mInfo(std::move(info))
{}
ErrorMessage::FileLocation::FileLocation(const Token* tok, const TokenList* tokenList)
: fileIndex(tok->fileIndex()), line(tok->linenr()), column(tok->column()), mOrigFileName(tokenList->getOrigFile(tok)), mFileName(Path::simplifyPath(tokenList->file(tok)))
{}
ErrorMessage::FileLocation::FileLocation(const Token* tok, std::string info, const TokenList* tokenList)
: fileIndex(tok->fileIndex()), line(tok->linenr()), column(tok->column()), mOrigFileName(tokenList->getOrigFile(tok)), mFileName(Path::simplifyPath(tokenList->file(tok))), mInfo(std::move(info))
{}
std::string ErrorMessage::FileLocation::getfile(bool convert) const
{
if (convert)
return Path::toNativeSeparators(mFileName);
return mFileName;
}
std::string ErrorMessage::FileLocation::getOrigFile(bool convert) const
{
if (convert)
return Path::toNativeSeparators(mOrigFileName);
return mOrigFileName;
}
void ErrorMessage::FileLocation::setfile(std::string file)
{
mFileName = Path::simplifyPath(std::move(file));
}
std::string ErrorMessage::FileLocation::stringify(bool addcolumn) const
{
std::string str;
str += '[';
str += Path::toNativeSeparators(mFileName);
if (line != SuppressionList::Suppression::NO_LINE) { // TODO: should not depend on Suppression
str += ':';
str += std::to_string(line);
if (addcolumn) {
str += ':';
str += std::to_string(column);
}
}
str += ']';
return str;
}
std::string ErrorLogger::toxml(const std::string &str)
{
std::string xml;
for (const unsigned char c : str) {
switch (c) {
case '<':
xml += "<";
break;
case '>':
xml += ">";
break;
case '&':
xml += "&";
break;
case '\"':
xml += """;
break;
case '\'':
xml += "'";
break;
case '\0':
xml += "\\0";
break;
case '\n':
xml += "
";
break;
case '\t':
xml += " ";
break;
case '\r':
xml += "
";
break;
default:
if (c >= ' ' && c <= 0x7f)
xml += c;
else
xml += 'x';
break;
}
}
return xml;
}
std::string ErrorLogger::plistHeader(const std::string &version, const std::vector &files)
{
std::ostringstream ostr;
ostr << "\r\n"
<< "\r\n"
<< "\r\n"
<< "\r\n"
<< " clang_version\r\n"
<< "cppcheck version " << version << "\r\n"
<< " files\r\n"
<< " \r\n";
for (const std::string & file : files)
ostr << " " << ErrorLogger::toxml(file) << "\r\n";
ostr << " \r\n"
<< " diagnostics\r\n"
<< " \r\n";
return ostr.str();
}
static std::string plistLoc(const char indent[], const ErrorMessage::FileLocation &loc)
{
std::ostringstream ostr;
ostr << indent << "\r\n"
<< indent << ' ' << "line" << loc.line << "\r\n"
<< indent << ' ' << "col" << loc.column << "\r\n"
<< indent << ' ' << "file" << loc.fileIndex << "\r\n"
<< indent << "\r\n";
return ostr.str();
}
std::string ErrorLogger::plistData(const ErrorMessage &msg)
{
std::ostringstream plist;
plist << " \r\n"
<< " path\r\n"
<< " \r\n";
auto prev = msg.callStack.cbegin();
for (auto it = msg.callStack.cbegin(); it != msg.callStack.cend(); ++it) {
if (prev != it) {
plist << " \r\n"
<< " kindcontrol\r\n"
<< " edges\r\n"
<< " \r\n"
<< " \r\n"
<< " start\r\n"
<< " \r\n"
<< plistLoc(" ", *prev)
<< plistLoc(" ", *prev)
<< " \r\n"
<< " end\r\n"
<< " \r\n"
<< plistLoc(" ", *it)
<< plistLoc(" ", *it)
<< " \r\n"
<< " \r\n"
<< " \r\n"
<< " \r\n";
prev = it;
}
auto next = it;
++next;
const std::string message = (it->getinfo().empty() && next == msg.callStack.cend() ? msg.shortMessage() : it->getinfo());
plist << " \r\n"
<< " kindevent\r\n"
<< " location\r\n"
<< plistLoc(" ", *it)
<< " ranges\r\n"
<< " \r\n"
<< " \r\n"
<< plistLoc(" ", *it)
<< plistLoc(" ", *it)
<< " \r\n"
<< " \r\n"
<< " depth0\r\n"
<< " extended_message\r\n"
<< " " << ErrorLogger::toxml(message) << "\r\n"
<< " message\r\n"
<< " " << ErrorLogger::toxml(message) << "\r\n"
<< " \r\n";
}
plist << " \r\n"
<< " description" << ErrorLogger::toxml(msg.shortMessage()) << "\r\n"
<< " category" << severityToString(msg.severity) << "\r\n"
<< " type" << ErrorLogger::toxml(msg.shortMessage()) << "\r\n"
<< " check_name" << msg.id << "\r\n"
<< " \r\n"
<< " issue_hash_content_of_line_in_context" << 0 << "\r\n"
<< " issue_context_kind\r\n"
<< " issue_context\r\n"
<< " issue_hash_function_offset\r\n"
<< " location\r\n"
<< plistLoc(" ", msg.callStack.back())
<< " \r\n";
return plist.str();
}
std::string replaceStr(std::string s, const std::string &from, const std::string &to)
{
std::string::size_type pos1 = 0;
while (pos1 < s.size()) {
pos1 = s.find(from, pos1);
if (pos1 == std::string::npos)
return s;
if (pos1 > 0 && (s[pos1-1] == '_' || std::isalnum(s[pos1-1]))) {
pos1++;
continue;
}
const std::string::size_type pos2 = pos1 + from.size();
if (pos2 >= 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;
}