Coverage Report

Created: 2025-01-24 06:31

/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 += "&lt;";
766
0
            break;
767
0
        case '>':
768
0
            xml += "&gt;";
769
0
            break;
770
0
        case '&':
771
0
            xml += "&amp;";
772
0
            break;
773
0
        case '\"':
774
0
            xml += "&quot;";
775
0
            break;
776
0
        case '\'':
777
0
            xml += "&apos;";
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
}