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