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