Cppcheck
errorlogger.cpp
Go to the documentation of this file.
1 /*
2  * Cppcheck - A tool for static C/C++ code analysis
3  * Copyright (C) 2007-2024 Cppcheck team.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "errorlogger.h"
20 
21 #include "color.h"
22 #include "cppcheck.h"
23 #include "path.h"
24 #include "settings.h"
25 #include "suppressions.h"
26 #include "token.h"
27 #include "tokenlist.h"
28 #include "utils.h"
29 
30 #include <algorithm>
31 #include <array>
32 #include <cassert>
33 #include <cctype>
34 #include <cstring>
35 #include <fstream>
36 #include <iomanip>
37 #include <sstream>
38 #include <string>
39 #include <unordered_map>
40 #include <utility>
41 
42 #include "xml.h"
43 
44 const std::set<std::string> ErrorLogger::mCriticalErrorIds{
45  "cppcheckError",
46  "cppcheckLimit",
47  "internalAstError",
48  "instantiationError",
49  "internalError",
50  "premium-internalError",
51  "premium-invalidArgument",
52  "premium-invalidLicense",
53  "preprocessorErrorDirective",
54  "syntaxError",
55  "unknownMacro"
56 };
57 
59  : severity(Severity::none), cwe(0U), certainty(Certainty::normal), hash(0)
60 {}
61 
62 // TODO: id and msg are swapped compared to other calls
63 ErrorMessage::ErrorMessage(std::list<FileLocation> callStack, std::string file1, Severity severity, const std::string &msg, std::string id, Certainty certainty) :
64  callStack(std::move(callStack)), // locations for this error message
65  id(std::move(id)), // set the message id
66  file0(std::move(file1)),
67  severity(severity), // severity for this error message
68  cwe(0U),
69  certainty(certainty),
70  hash(0)
71 {
72  // set the summary and verbose messages
73  setmsg(msg);
74 }
75 
76 
77 // TODO: id and msg are swapped compared to other calls
78 ErrorMessage::ErrorMessage(std::list<FileLocation> callStack, std::string file1, Severity severity, const std::string &msg, std::string id, const CWE &cwe, Certainty certainty) :
79  callStack(std::move(callStack)), // locations for this error message
80  id(std::move(id)), // set the message id
81  file0(std::move(file1)),
82  severity(severity), // severity for this error message
83  cwe(cwe.id),
84  certainty(certainty),
85  hash(0)
86 {
87  // set the summary and verbose messages
88  setmsg(msg);
89 }
90 
91 ErrorMessage::ErrorMessage(const std::list<const Token*>& callstack, const TokenList* list, Severity severity, std::string id, const std::string& msg, Certainty certainty)
92  : id(std::move(id)), severity(severity), cwe(0U), certainty(certainty), hash(0)
93 {
94  // Format callstack
95  for (std::list<const Token *>::const_iterator it = callstack.cbegin(); it != callstack.cend(); ++it) {
96  // --errorlist can provide null values here
97  if (!(*it))
98  continue;
99 
100  callStack.emplace_back(*it, list);
101  }
102 
103  if (list && !list->getFiles().empty())
104  file0 = list->getFiles()[0];
105 
106  setmsg(msg);
107 }
108 
109 
110 ErrorMessage::ErrorMessage(const std::list<const Token*>& callstack, const TokenList* list, Severity severity, std::string id, const std::string& msg, const CWE &cwe, Certainty certainty)
111  : id(std::move(id)), severity(severity), cwe(cwe.id), certainty(certainty)
112 {
113  // Format callstack
114  for (const Token *tok: callstack) {
115  // --errorlist can provide null values here
116  if (!tok)
117  continue;
118 
119  callStack.emplace_back(tok, list);
120  }
121 
122  if (list && !list->getFiles().empty())
123  file0 = list->getFiles()[0];
124 
125  setmsg(msg);
126 
127  hash = 0; // calculateWarningHash(list, hashWarning.str());
128 }
129 
130 ErrorMessage::ErrorMessage(const ErrorPath &errorPath, const TokenList *tokenList, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty)
131  : id(id), severity(severity), cwe(cwe.id), certainty(certainty)
132 {
133  // Format callstack
134  for (const ErrorPathItem& e: errorPath) {
135  const Token *tok = e.first;
136  // --errorlist can provide null values here
137  if (!tok)
138  continue;
139 
140  const std::string& path_info = e.second;
141 
142  std::string info;
143  if (startsWith(path_info,"$symbol:") && path_info.find('\n') < path_info.size()) {
144  const std::string::size_type pos = path_info.find('\n');
145  const std::string symbolName = path_info.substr(8, pos - 8);
146  info = replaceStr(path_info.substr(pos+1), "$symbol", symbolName);
147  }
148  else {
149  info = path_info;
150  }
151 
152  callStack.emplace_back(tok, std::move(info), tokenList);
153  }
154 
155  if (tokenList && !tokenList->getFiles().empty())
156  file0 = tokenList->getFiles()[0];
157 
158  setmsg(msg);
159 
160  hash = 0; // calculateWarningHash(tokenList, hashWarning.str());
161 }
162 
163 ErrorMessage::ErrorMessage(const tinyxml2::XMLElement * const errmsg)
164  : severity(Severity::none),
165  cwe(0U),
166  certainty(Certainty::normal)
167 {
168  const char * const unknown = "<UNKNOWN>";
169 
170  const char *attr = errmsg->Attribute("id");
171  id = attr ? attr : unknown;
172 
173  attr = errmsg->Attribute("severity");
174  severity = attr ? severityFromString(attr) : Severity::none;
175 
176  attr = errmsg->Attribute("cwe");
177  // cppcheck-suppress templateInstantiation - TODO: fix this - see #11631
178  cwe.id = attr ? strToInt<unsigned short>(attr) : 0;
179 
180  attr = errmsg->Attribute("inconclusive");
181  certainty = (attr && (std::strcmp(attr, "true") == 0)) ? Certainty::inconclusive : Certainty::normal;
182 
183  attr = errmsg->Attribute("msg");
184  mShortMessage = attr ? attr : "";
185 
186  attr = errmsg->Attribute("verbose");
187  mVerboseMessage = attr ? attr : "";
188 
189  attr = errmsg->Attribute("hash");
190  hash = attr ? strToInt<std::size_t>(attr) : 0;
191 
192  for (const tinyxml2::XMLElement *e = errmsg->FirstChildElement(); e; e = e->NextSiblingElement()) {
193  if (std::strcmp(e->Name(),"location")==0) {
194  const char *strfile = e->Attribute("file");
195  const char *strinfo = e->Attribute("info");
196  const char *strline = e->Attribute("line");
197  const char *strcolumn = e->Attribute("column");
198 
199  const char *file = strfile ? strfile : unknown;
200  const char *info = strinfo ? strinfo : "";
201  const int line = strline ? strToInt<int>(strline) : 0;
202  const int column = strcolumn ? strToInt<int>(strcolumn) : 0;
203  callStack.emplace_front(file, info, line, column);
204  } else if (std::strcmp(e->Name(),"symbol")==0) {
205  mSymbolNames += e->GetText();
206  }
207  }
208 }
209 
210 void ErrorMessage::setmsg(const std::string &msg)
211 {
212  // If a message ends to a '\n' and contains only a one '\n'
213  // it will cause the mVerboseMessage to be empty which will show
214  // as an empty message to the user if --verbose is used.
215  // Even this doesn't cause problems with messages that have multiple
216  // lines, none of the error messages should end into it.
217  assert(!endsWith(msg,'\n'));
218 
219  // The summary and verbose message are separated by a newline
220  // If there is no newline then both the summary and verbose messages
221  // are the given message
222  const std::string::size_type pos = msg.find('\n');
223  const std::string symbolName = mSymbolNames.empty() ? std::string() : mSymbolNames.substr(0, mSymbolNames.find('\n'));
224  if (pos == std::string::npos) {
225  mShortMessage = replaceStr(msg, "$symbol", symbolName);
226  mVerboseMessage = replaceStr(msg, "$symbol", symbolName);
227  } else if (startsWith(msg,"$symbol:")) {
228  mSymbolNames += msg.substr(8, pos-7);
229  setmsg(msg.substr(pos + 1));
230  } else {
231  mShortMessage = replaceStr(msg.substr(0, pos), "$symbol", symbolName);
232  mVerboseMessage = replaceStr(msg.substr(pos + 1), "$symbol", symbolName);
233  }
234 }
235 
236 static void serializeString(std::string &oss, const std::string & str)
237 {
238  oss += std::to_string(str.length());
239  oss += " ";
240  oss += str;
241 }
242 
243 ErrorMessage ErrorMessage::fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename, const std::string& msg)
244 {
245  if (internalError.token)
246  assert(tokenList != nullptr); // we need to make sure we can look up the provided token
247 
248  std::list<ErrorMessage::FileLocation> locationList;
249  if (tokenList && internalError.token) {
250  locationList.emplace_back(internalError.token, tokenList);
251  } else {
252  locationList.emplace_back(filename, 0, 0);
253  if (tokenList && (filename != tokenList->getSourceFilePath())) {
254  locationList.emplace_back(tokenList->getSourceFilePath(), 0, 0);
255  }
256  }
257  ErrorMessage errmsg(std::move(locationList),
258  tokenList ? tokenList->getSourceFilePath() : filename,
260  (msg.empty() ? "" : (msg + ": ")) + internalError.errorMessage,
261  internalError.id,
263  // TODO: find a better way
264  if (!internalError.details.empty())
265  errmsg.mVerboseMessage = errmsg.mVerboseMessage + ": " + internalError.details;
266  return errmsg;
267 }
268 
269 std::string ErrorMessage::serialize() const
270 {
271  // Serialize this message into a simple string
272  std::string oss;
273  serializeString(oss, id);
275  serializeString(oss, std::to_string(cwe.id));
276  serializeString(oss, std::to_string(hash));
277  serializeString(oss, file0);
279  const std::string text("inconclusive");
280  serializeString(oss, text);
281  }
282 
283  const std::string saneShortMessage = fixInvalidChars(mShortMessage);
284  const std::string saneVerboseMessage = fixInvalidChars(mVerboseMessage);
285 
286  serializeString(oss, saneShortMessage);
287  serializeString(oss, saneVerboseMessage);
288  oss += std::to_string(callStack.size());
289  oss += " ";
290 
291  for (std::list<ErrorMessage::FileLocation>::const_iterator loc = callStack.cbegin(); loc != callStack.cend(); ++loc) {
292  std::string frame;
293  frame += std::to_string(loc->line);
294  frame += '\t';
295  frame += std::to_string(loc->column);
296  frame += '\t';
297  frame += loc->getfile(false);
298  frame += '\t';
299  frame += loc->getOrigFile(false);
300  frame += '\t';
301  frame += loc->getinfo();
302  serializeString(oss, frame);
303  }
304 
305  return oss;
306 }
307 
308 void ErrorMessage::deserialize(const std::string &data)
309 {
310  // TODO: clear all fields
312  callStack.clear();
313 
314  std::istringstream iss(data);
315  std::array<std::string, 7> results;
316  std::size_t elem = 0;
317  while (iss.good() && elem < 7) {
318  unsigned int len = 0;
319  if (!(iss >> len))
320  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid length");
321 
322  if (iss.get() != ' ')
323  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid separator");
324 
325  if (!iss.good())
326  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data");
327 
328  std::string temp;
329  if (len > 0) {
330  temp.resize(len);
331  iss.read(&temp[0], len);
332 
333  if (!iss.good())
334  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data");
335 
336  if (temp == "inconclusive") {
338  continue;
339  }
340  }
341 
342  results[elem++] = std::move(temp);
343  }
344 
345  if (!iss.good())
346  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data");
347 
348  if (elem != 7)
349  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - insufficient elements");
350 
351  id = std::move(results[0]);
352  severity = severityFromString(results[1]);
353  cwe.id = 0;
354  if (!results[2].empty()) {
355  std::string err;
356  if (!strToInt(results[2], cwe.id, &err))
357  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid CWE ID - " + err);
358  }
359  hash = 0;
360  if (!results[3].empty()) {
361  std::string err;
362  if (!strToInt(results[3], hash, &err))
363  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid hash - " + err);
364  }
365  file0 = std::move(results[4]);
366  mShortMessage = std::move(results[5]);
367  mVerboseMessage = std::move(results[6]);
368 
369  unsigned int stackSize = 0;
370  if (!(iss >> stackSize))
371  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid stack size");
372 
373  if (iss.get() != ' ')
374  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid separator");
375 
376  if (stackSize == 0)
377  return;
378 
379  while (iss.good()) {
380  unsigned int len = 0;
381  if (!(iss >> len))
382  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid length (stack)");
383 
384  if (iss.get() != ' ')
385  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - invalid separator (stack)");
386 
387  std::string temp;
388  if (len > 0) {
389  temp.resize(len);
390  iss.read(&temp[0], len);
391 
392  if (!iss.good())
393  throw InternalError(nullptr, "Internal Error: Deserialization of error message failed - premature end of data (stack)");
394  }
395 
396  std::vector<std::string> substrings;
397  substrings.reserve(5);
398  for (std::string::size_type pos = 0; pos < temp.size() && substrings.size() < 5; ++pos) {
399  if (substrings.size() == 4) {
400  substrings.push_back(temp.substr(pos));
401  break;
402  }
403  const std::string::size_type start = pos;
404  pos = temp.find('\t', pos);
405  if (pos == std::string::npos) {
406  substrings.push_back(temp.substr(start));
407  break;
408  }
409  substrings.push_back(temp.substr(start, pos - start));
410  }
411  if (substrings.size() < 4)
412  throw InternalError(nullptr, "Internal Error: Deserializing of error message failed");
413 
414  // (*loc).line << '\t' << (*loc).column << '\t' << (*loc).getfile(false) << '\t' << loc->getOrigFile(false) << '\t' << loc->getinfo();
415 
416  std::string info;
417  if (substrings.size() == 5)
418  info = std::move(substrings[4]);
419  ErrorMessage::FileLocation loc(substrings[3], std::move(info), strToInt<int>(substrings[0]), strToInt<unsigned int>(substrings[1]));
420  loc.setfile(std::move(substrings[2]));
421 
422  callStack.push_back(std::move(loc));
423 
424  if (callStack.size() >= stackSize)
425  break;
426  }
427 }
428 
429 std::string ErrorMessage::getXMLHeader(std::string productName)
430 {
431  const auto nameAndVersion = Settings::getNameAndVersion(productName);
432  productName = nameAndVersion.first;
433  const std::string version = nameAndVersion.first.empty() ? CppCheck::version() : nameAndVersion.second;
434 
435  tinyxml2::XMLPrinter printer;
436 
437  // standard xml header
438  printer.PushDeclaration("xml version=\"1.0\" encoding=\"UTF-8\"");
439 
440  // header
441  printer.OpenElement("results", false);
442 
443  printer.PushAttribute("version", 2);
444  printer.OpenElement("cppcheck", false);
445  if (!productName.empty())
446  printer.PushAttribute("product-name", productName.c_str());
447  printer.PushAttribute("version", version.c_str());
448  printer.CloseElement(false);
449  printer.OpenElement("errors", false);
450 
451  return std::string(printer.CStr()) + '>';
452 }
453 
455 {
456  return " </errors>\n</results>";
457 }
458 
459 // There is no utf-8 support around but the strings should at least be safe for to tinyxml2.
460 // See #5300 "Invalid encoding in XML output" and #6431 "Invalid XML created - Invalid encoding of string literal "
461 std::string ErrorMessage::fixInvalidChars(const std::string& raw)
462 {
463  std::string result;
464  result.reserve(raw.length());
465  std::string::const_iterator from=raw.cbegin();
466  while (from!=raw.cend()) {
467  if (std::isprint(static_cast<unsigned char>(*from))) {
468  result.push_back(*from);
469  } else {
470  std::ostringstream es;
471  // straight cast to (unsigned) doesn't work out.
472  const unsigned uFrom = (unsigned char)*from;
473  es << '\\' << std::setbase(8) << std::setw(3) << std::setfill('0') << uFrom;
474  result += es.str();
475  }
476  ++from;
477  }
478  return result;
479 }
480 
481 std::string ErrorMessage::toXML() const
482 {
483  tinyxml2::XMLPrinter printer(nullptr, false, 2);
484  printer.OpenElement("error", false);
485  printer.PushAttribute("id", id.c_str());
486  printer.PushAttribute("severity", severityToString(severity).c_str());
487  printer.PushAttribute("msg", fixInvalidChars(mShortMessage).c_str());
488  printer.PushAttribute("verbose", fixInvalidChars(mVerboseMessage).c_str());
489  if (cwe.id)
490  printer.PushAttribute("cwe", cwe.id);
491  if (hash)
492  printer.PushAttribute("hash", std::to_string(hash).c_str());
494  printer.PushAttribute("inconclusive", "true");
495 
496  if (!file0.empty())
497  printer.PushAttribute("file0", file0.c_str());
498 
499  for (std::list<FileLocation>::const_reverse_iterator it = callStack.crbegin(); it != callStack.crend(); ++it) {
500  printer.OpenElement("location", false);
501  printer.PushAttribute("file", it->getfile().c_str());
502  printer.PushAttribute("line", std::max(it->line,0));
503  printer.PushAttribute("column", it->column);
504  if (!it->getinfo().empty())
505  printer.PushAttribute("info", fixInvalidChars(it->getinfo()).c_str());
506  printer.CloseElement(false);
507  }
508  for (std::string::size_type pos = 0; pos < mSymbolNames.size();) {
509  const std::string::size_type pos2 = mSymbolNames.find('\n', pos);
510  std::string symbolName;
511  if (pos2 == std::string::npos) {
512  symbolName = mSymbolNames.substr(pos);
513  pos = pos2;
514  } else {
515  symbolName = mSymbolNames.substr(pos, pos2-pos);
516  pos = pos2 + 1;
517  }
518  printer.OpenElement("symbol", false);
519  printer.PushText(symbolName.c_str());
520  printer.CloseElement(false);
521  }
522  printer.CloseElement(false);
523  return printer.CStr();
524 }
525 
526 // TODO: read info from some shared resource instead?
527 static std::string readCode(const std::string &file, int linenr, int column, const char endl[])
528 {
529  std::ifstream fin(file);
530  std::string line;
531  while (linenr > 0 && std::getline(fin,line)) {
532  linenr--;
533  }
534  const std::string::size_type endPos = line.find_last_not_of("\r\n\t ");
535  if (endPos + 1 < line.size())
536  line.erase(endPos + 1);
537  std::string::size_type pos = 0;
538  while ((pos = line.find('\t', pos)) != std::string::npos)
539  line[pos] = ' ';
540  return line + endl + std::string((column>0 ? column-1 : 0), ' ') + '^';
541 }
542 
543 static void replaceSpecialChars(std::string& source)
544 {
545  // 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
546  // Substitution should be done first so messages from cppcheck never get translated.
547  static const std::unordered_map<char, std::string> substitutionMap = {
548  {'b', "\b"},
549  {'n', "\n"},
550  {'r', "\r"},
551  {'t', "\t"}
552  };
553 
554  std::string::size_type index = 0;
555  while ((index = source.find('\\', index)) != std::string::npos) {
556  const char searchFor = source[index+1];
557  const auto it = substitutionMap.find(searchFor);
558  if (it == substitutionMap.end()) {
559  index += 1;
560  continue;
561  }
562  const std::string& replaceWith = it->second;
563  source.replace(index, 2, replaceWith);
564  index += replaceWith.length();
565  }
566 }
567 
568 static void replace(std::string& source, const std::unordered_map<std::string, std::string> &substitutionMap)
569 {
570  std::string::size_type index = 0;
571  while ((index = source.find('{', index)) != std::string::npos) {
572  const std::string::size_type end = source.find('}', index);
573  if (end == std::string::npos)
574  break;
575  const std::string searchFor = source.substr(index, end-index+1);
576  const auto it = substitutionMap.find(searchFor);
577  if (it == substitutionMap.end()) {
578  index += 1;
579  continue;
580  }
581  const std::string& replaceWith = it->second;
582  source.replace(index, searchFor.length(), replaceWith);
583  index += replaceWith.length();
584  }
585 }
586 
587 static void replaceColors(std::string& source) {
588  // TODO: colors are not applied when either stdout or stderr is not a TTY because we resolve them before the stream usage
589  static const std::unordered_map<std::string, std::string> substitutionMap =
590  {
591  {"{reset}", ::toString(Color::Reset)},
592  {"{bold}", ::toString(Color::Bold)},
593  {"{dim}", ::toString(Color::Dim)},
594  {"{red}", ::toString(Color::FgRed)},
595  {"{green}", ::toString(Color::FgGreen)},
596  {"{blue}", ::toString(Color::FgBlue)},
597  {"{magenta}", ::toString(Color::FgMagenta)},
598  {"{default}", ::toString(Color::FgDefault)},
599  };
600  replace(source, substitutionMap);
601 }
602 
603 // TODO: remove default parameters
604 std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation) const
605 {
606  // Save this ErrorMessage in plain text.
607 
608  // TODO: should never happen - remove this
609  // No template is given
610  // (not 100%) equivalent templateFormat: {callstack} ({severity}{inconclusive:, inconclusive}) {message}
611  if (templateFormat.empty()) {
612  std::string text;
613  if (!callStack.empty()) {
615  text += ": ";
616  }
617  if (severity != Severity::none) {
618  text += '(';
619  text += severityToString(severity);
621  text += ", inconclusive";
622  text += ") ";
623  }
624  text += (verbose ? mVerboseMessage : mShortMessage);
625  return text;
626  }
627 
628  // template is given. Reformat the output according to it
629  std::string result = templateFormat;
630 
631  findAndReplace(result, "{id}", id);
632 
633  std::string::size_type pos1 = result.find("{inconclusive:");
634  while (pos1 != std::string::npos) {
635  const std::string::size_type pos2 = result.find('}', pos1+1);
636  const std::string replaceFrom = result.substr(pos1,pos2-pos1+1);
637  const std::string replaceWith = (certainty == Certainty::inconclusive) ? result.substr(pos1+14, pos2-pos1-14) : std::string();
638  findAndReplace(result, replaceFrom, replaceWith);
639  pos1 = result.find("{inconclusive:", pos1);
640  }
641  findAndReplace(result, "{severity}", severityToString(severity));
642  findAndReplace(result, "{cwe}", std::to_string(cwe.id));
643  findAndReplace(result, "{message}", verbose ? mVerboseMessage : mShortMessage);
644  if (!callStack.empty()) {
645  if (result.find("{callstack}") != std::string::npos)
647  findAndReplace(result, "{file}", callStack.back().getfile());
648  findAndReplace(result, "{line}", std::to_string(callStack.back().line));
649  findAndReplace(result, "{column}", std::to_string(callStack.back().column));
650  if (result.find("{code}") != std::string::npos) {
651  const std::string::size_type pos = result.find('\r');
652  const char *endl;
653  if (pos == std::string::npos)
654  endl = "\n";
655  else if (pos+1 < result.size() && result[pos+1] == '\n')
656  endl = "\r\n";
657  else
658  endl = "\r";
659  findAndReplace(result, "{code}", readCode(callStack.back().getOrigFile(), callStack.back().line, callStack.back().column, endl));
660  }
661  } else {
662  static const std::unordered_map<std::string, std::string> callStackSubstitutionMap =
663  {
664  {"{callstack}", ""},
665  {"{file}", "nofile"},
666  {"{line}", "0"},
667  {"{column}", "0"},
668  {"{code}", ""}
669  };
670  replace(result, callStackSubstitutionMap);
671  }
672 
673  if (!templateLocation.empty() && callStack.size() >= 2U) {
674  for (const FileLocation &fileLocation : callStack) {
675  std::string text = templateLocation;
676 
677  findAndReplace(text, "{file}", fileLocation.getfile());
678  findAndReplace(text, "{line}", std::to_string(fileLocation.line));
679  findAndReplace(text, "{column}", std::to_string(fileLocation.column));
680  findAndReplace(text, "{info}", fileLocation.getinfo().empty() ? mShortMessage : fileLocation.getinfo());
681  if (text.find("{code}") != std::string::npos) {
682  const std::string::size_type pos = text.find('\r');
683  const char *endl;
684  if (pos == std::string::npos)
685  endl = "\n";
686  else if (pos+1 < text.size() && text[pos+1] == '\n')
687  endl = "\r\n";
688  else
689  endl = "\r";
690  findAndReplace(text, "{code}", readCode(fileLocation.getOrigFile(), fileLocation.line, fileLocation.column, endl));
691  }
692  result += '\n' + text;
693  }
694  }
695 
696  return result;
697 }
698 
699 std::string ErrorLogger::callStackToString(const std::list<ErrorMessage::FileLocation> &callStack)
700 {
701  std::string str;
702  for (std::list<ErrorMessage::FileLocation>::const_iterator tok = callStack.cbegin(); tok != callStack.cend(); ++tok) {
703  str += (tok == callStack.cbegin() ? "" : " -> ");
704  str += tok->stringify();
705  }
706  return str;
707 }
708 
709 
711  : fileIndex(tok->fileIndex()), line(tok->linenr()), column(tok->column()), mOrigFileName(tokenList->getOrigFile(tok)), mFileName(tokenList->file(tok))
712 {}
713 
714 ErrorMessage::FileLocation::FileLocation(const Token* tok, std::string info, const TokenList* tokenList)
715  : fileIndex(tok->fileIndex()), line(tok->linenr()), column(tok->column()), mOrigFileName(tokenList->getOrigFile(tok)), mFileName(tokenList->file(tok)), mInfo(std::move(info))
716 {}
717 
718 std::string ErrorMessage::FileLocation::getfile(bool convert) const
719 {
720  if (convert)
721  return Path::toNativeSeparators(mFileName);
722  return mFileName;
723 }
724 
725 std::string ErrorMessage::FileLocation::getOrigFile(bool convert) const
726 {
727  if (convert)
728  return Path::toNativeSeparators(mOrigFileName);
729  return mOrigFileName;
730 }
731 
733 {
734  mFileName = Path::fromNativeSeparators(std::move(file));
735  mFileName = Path::simplifyPath(std::move(mFileName));
736 }
737 
739 {
740  std::string str;
741  str += '[';
742  str += Path::toNativeSeparators(mFileName);
744  str += ':';
745  str += std::to_string(line);
746  }
747  str += ']';
748  return str;
749 }
750 
751 std::string ErrorLogger::toxml(const std::string &str)
752 {
753  std::string xml;
754  for (const unsigned char c : str) {
755  switch (c) {
756  case '<':
757  xml += "&lt;";
758  break;
759  case '>':
760  xml += "&gt;";
761  break;
762  case '&':
763  xml += "&amp;";
764  break;
765  case '\"':
766  xml += "&quot;";
767  break;
768  case '\'':
769  xml += "&apos;";
770  break;
771  case '\0':
772  xml += "\\0";
773  break;
774  default:
775  if (c >= ' ' && c <= 0x7f)
776  xml += c;
777  else
778  xml += 'x';
779  break;
780  }
781  }
782  return xml;
783 }
784 
785 std::string ErrorLogger::plistHeader(const std::string &version, const std::vector<std::string> &files)
786 {
787  std::ostringstream ostr;
788  ostr << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
789  << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"
790  << "<plist version=\"1.0\">\r\n"
791  << "<dict>\r\n"
792  << " <key>clang_version</key>\r\n"
793  << "<string>cppcheck version " << version << "</string>\r\n"
794  << " <key>files</key>\r\n"
795  << " <array>\r\n";
796  for (const std::string & file : files)
797  ostr << " <string>" << ErrorLogger::toxml(file) << "</string>\r\n";
798  ostr << " </array>\r\n"
799  << " <key>diagnostics</key>\r\n"
800  << " <array>\r\n";
801  return ostr.str();
802 }
803 
804 static std::string plistLoc(const char indent[], const ErrorMessage::FileLocation &loc)
805 {
806  std::ostringstream ostr;
807  ostr << indent << "<dict>\r\n"
808  << indent << ' ' << "<key>line</key><integer>" << loc.line << "</integer>\r\n"
809  << indent << ' ' << "<key>col</key><integer>" << loc.column << "</integer>\r\n"
810  << indent << ' ' << "<key>file</key><integer>" << loc.fileIndex << "</integer>\r\n"
811  << indent << "</dict>\r\n";
812  return ostr.str();
813 }
814 
815 std::string ErrorLogger::plistData(const ErrorMessage &msg)
816 {
817  std::ostringstream plist;
818  plist << " <dict>\r\n"
819  << " <key>path</key>\r\n"
820  << " <array>\r\n";
821 
822  std::list<ErrorMessage::FileLocation>::const_iterator prev = msg.callStack.cbegin();
823 
824  for (std::list<ErrorMessage::FileLocation>::const_iterator it = msg.callStack.cbegin(); it != msg.callStack.cend(); ++it) {
825  if (prev != it) {
826  plist << " <dict>\r\n"
827  << " <key>kind</key><string>control</string>\r\n"
828  << " <key>edges</key>\r\n"
829  << " <array>\r\n"
830  << " <dict>\r\n"
831  << " <key>start</key>\r\n"
832  << " <array>\r\n"
833  << plistLoc(" ", *prev)
834  << plistLoc(" ", *prev)
835  << " </array>\r\n"
836  << " <key>end</key>\r\n"
837  << " <array>\r\n"
838  << plistLoc(" ", *it)
839  << plistLoc(" ", *it)
840  << " </array>\r\n"
841  << " </dict>\r\n"
842  << " </array>\r\n"
843  << " </dict>\r\n";
844  prev = it;
845  }
846 
847  std::list<ErrorMessage::FileLocation>::const_iterator next = it;
848  ++next;
849  const std::string message = (it->getinfo().empty() && next == msg.callStack.cend() ? msg.shortMessage() : it->getinfo());
850 
851  plist << " <dict>\r\n"
852  << " <key>kind</key><string>event</string>\r\n"
853  << " <key>location</key>\r\n"
854  << plistLoc(" ", *it)
855  << " <key>ranges</key>\r\n"
856  << " <array>\r\n"
857  << " <array>\r\n"
858  << plistLoc(" ", *it)
859  << plistLoc(" ", *it)
860  << " </array>\r\n"
861  << " </array>\r\n"
862  << " <key>depth</key><integer>0</integer>\r\n"
863  << " <key>extended_message</key>\r\n"
864  << " <string>" << ErrorLogger::toxml(message) << "</string>\r\n"
865  << " <key>message</key>\r\n"
866  << " <string>" << ErrorLogger::toxml(message) << "</string>\r\n"
867  << " </dict>\r\n";
868  }
869 
870  plist << " </array>\r\n"
871  << " <key>description</key><string>" << ErrorLogger::toxml(msg.shortMessage()) << "</string>\r\n"
872  << " <key>category</key><string>" << severityToString(msg.severity) << "</string>\r\n"
873  << " <key>type</key><string>" << ErrorLogger::toxml(msg.shortMessage()) << "</string>\r\n"
874  << " <key>check_name</key><string>" << msg.id << "</string>\r\n"
875  << " <!-- This hash is experimental and going to change! -->\r\n"
876  << " <key>issue_hash_content_of_line_in_context</key><string>" << 0 << "</string>\r\n"
877  << " <key>issue_context_kind</key><string></string>\r\n"
878  << " <key>issue_context</key><string></string>\r\n"
879  << " <key>issue_hash_function_offset</key><string></string>\r\n"
880  << " <key>location</key>\r\n"
881  << plistLoc(" ", msg.callStack.back())
882  << " </dict>\r\n";
883  return plist.str();
884 }
885 
886 
887 std::string replaceStr(std::string s, const std::string &from, const std::string &to)
888 {
889  std::string::size_type pos1 = 0;
890  while (pos1 < s.size()) {
891  pos1 = s.find(from, pos1);
892  if (pos1 == std::string::npos)
893  return s;
894  if (pos1 > 0 && (s[pos1-1] == '_' || std::isalnum(s[pos1-1]))) {
895  pos1++;
896  continue;
897  }
898  const std::string::size_type pos2 = pos1 + from.size();
899  if (pos2 >= s.size())
900  return s.substr(0,pos1) + to;
901  if (s[pos2] == '_' || std::isalnum(s[pos2])) {
902  pos1++;
903  continue;
904  }
905  s.replace(pos1, from.size(), to);
906  pos1 += to.size();
907  }
908  return s;
909 }
910 
911 void substituteTemplateFormatStatic(std::string& templateFormat)
912 {
913  replaceSpecialChars(templateFormat);
914  replaceColors(templateFormat);
915 }
916 
917 void substituteTemplateLocationStatic(std::string& templateLocation)
918 {
919  replaceSpecialChars(templateLocation);
920  replaceColors(templateLocation);
921 }
static const char * version()
Returns current version number as a string.
Definition: cppcheck.cpp:354
static std::string plistData(const ErrorMessage &msg)
static std::string toxml(const std::string &str)
Convert XML-sensitive characters into XML entities.
static const std::set< std::string > mCriticalErrorIds
Definition: errorlogger.h:269
static std::string callStackToString(const std::list< ErrorMessage::FileLocation > &callStack)
static std::string plistHeader(const std::string &version, const std::vector< std::string > &files)
File name and line number.
Definition: errorlogger.h:55
void setfile(std::string file)
Set the filename.
std::string getfile(bool convert=true) const
Return the filename.
FileLocation(const std::string &file, int line, unsigned int column)
Definition: errorlogger.h:57
std::string stringify() const
std::string getOrigFile(bool convert=true) const
Filename with the whole path (no –rp)
Wrapper for error messages, provided by reportErr()
Definition: errorlogger.h:48
static std::string getXMLFooter()
static std::string getXMLHeader(std::string productName)
std::string mSymbolNames
symbol names
Definition: errorlogger.h:207
Severity severity
Definition: errorlogger.h:170
std::string mVerboseMessage
Verbose message.
Definition: errorlogger.h:204
std::size_t hash
Warning hash.
Definition: errorlogger.h:175
std::string toString(bool verbose, const std::string &templateFormat=emptyString, const std::string &templateLocation=emptyString) const
Format the error message into a string.
const std::string & shortMessage() const
Short message (single line short message)
Definition: errorlogger.h:181
std::string mShortMessage
Short message.
Definition: errorlogger.h:201
static std::string fixInvalidChars(const std::string &raw)
std::string file0
For GUI rechecking; source file (not header)
Definition: errorlogger.h:168
std::list< FileLocation > callStack
Definition: errorlogger.h:164
std::string serialize() const
std::string id
Definition: errorlogger.h:165
Certainty certainty
Definition: errorlogger.h:172
void deserialize(const std::string &data)
std::string toXML() const
Format the error message in XML format.
static ErrorMessage fromInternalError(const InternalError &internalError, const TokenList *tokenList, const std::string &filename, const std::string &msg=emptyString)
void setmsg(const std::string &msg)
set short and verbose messages
static std::string simplifyPath(std::string originalPath)
Simplify path "foo/bar/.." => "foo".
Definition: path.cpp:83
static std::string fromNativeSeparators(std::string path)
Convert path to use internal path separators.
Definition: path.cpp:75
static std::string toNativeSeparators(std::string path)
Convert path to use native separators.
Definition: path.cpp:62
static std::pair< std::string, std::string > getNameAndVersion(const std::string &productName)
Definition: settings.cpp:159
const std::string & getSourceFilePath() const
Definition: tokenlist.cpp:75
const std::vector< std::string > & getFiles() const
Get filenames (the sourcefile + the files it include).
Definition: tokenlist.h:141
The token list that the TokenList generates is a linked-list of this class.
Definition: token.h:150
std::string toString(Color c)
Definition: color.cpp:54
@ FgMagenta
@ FgDefault
static void serializeString(std::string &oss, const std::string &str)
static void replaceSpecialChars(std::string &source)
static std::string readCode(const std::string &file, int linenr, int column, const char endl[])
static void replaceColors(std::string &source)
static void replace(std::string &source, const std::unordered_map< std::string, std::string > &substitutionMap)
static std::string plistLoc(const char indent[], const ErrorMessage::FileLocation &loc)
Severity
enum class for severity.
Definition: errortypes.h:63
Certainty
Definition: errortypes.h:54
std::pair< const Token *, std::string > ErrorPathItem
Definition: errortypes.h:129
void substituteTemplateFormatStatic(std::string &templateFormat)
replaces the static parts of the location template
std::string replaceStr(std::string s, const std::string &from, const std::string &to)
Replace substring.
void substituteTemplateLocationStatic(std::string &templateLocation)
replaces the static parts of the location template
Severity severityFromString(const std::string &severity)
Definition: errortypes.cpp:76
std::string severityToString(Severity severity)
Definition: errortypes.cpp:50
std::list< ErrorPathItem > ErrorPath
Definition: errortypes.h:130
@ none
No severity (default value).
@ error
Programming error.
unsigned short id
Definition: errortypes.h:126
Simple container to be thrown when internal error is detected.
Definition: errortypes.h:36
std::string id
Definition: errortypes.h:46
std::string errorMessage
Definition: errortypes.h:43
std::string details
Definition: errortypes.h:44
const Token * token
Definition: errortypes.h:42
static void indent(std::string &str, const nonneg int indent1, const nonneg int indent2)
Definition: token.cpp:1742
void findAndReplace(std::string &source, const std::string &searchFor, const std::string &replaceWith)
Replace all occurrences of searchFor with replaceWith in the given source.
Definition: utils.cpp:142
bool startsWith(const std::string &str, const char start[], std::size_t startlen)
Definition: utils.h:94
bool strToInt(const std::string &str, T &num, std::string *err=nullptr)
Definition: utils.h:211
bool endsWith(const std::string &str, char c)
Definition: utils.h:110