Coverage Report

Created: 2023-09-25 06:15

/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 += "&lt;";
771
0
            break;
772
0
        case '>':
773
0
            xml += "&gt;";
774
0
            break;
775
0
        case '&':
776
0
            xml += "&amp;";
777
0
            break;
778
0
        case '\"':
779
0
            xml += "&quot;";
780
0
            break;
781
0
        case '\'':
782
0
            xml += "&apos;";
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
}