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