Line data Source code
1 : /*
2 : * Cppcheck - A tool for static C/C++ code analysis
3 : * Copyright (C) 2007-2024 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 "cppcheck.h"
20 :
21 : #include "addoninfo.h"
22 : #include "check.h"
23 : #include "checkunusedfunctions.h"
24 : #include "clangimport.h"
25 : #include "color.h"
26 : #include "ctu.h"
27 : #include "errortypes.h"
28 : #include "filesettings.h"
29 : #include "library.h"
30 : #include "path.h"
31 : #include "platform.h"
32 : #include "preprocessor.h"
33 : #include "standards.h"
34 : #include "suppressions.h"
35 : #include "timer.h"
36 : #include "token.h"
37 : #include "tokenize.h"
38 : #include "tokenlist.h"
39 : #include "utils.h"
40 : #include "valueflow.h"
41 : #include "version.h"
42 :
43 : #include <algorithm>
44 : #include <cassert>
45 : #include <cstdio>
46 : #include <cstdint>
47 : #include <cstring>
48 : #include <cctype>
49 : #include <cstdlib>
50 : #include <ctime>
51 : #include <exception> // IWYU pragma: keep
52 : #include <fstream>
53 : #include <iostream> // <- TEMPORARY
54 : #include <new>
55 : #include <set>
56 : #include <sstream>
57 : #include <stdexcept>
58 : #include <string>
59 : #include <unordered_set>
60 : #include <utility>
61 : #include <vector>
62 :
63 : #include "json.h"
64 :
65 : #include <simplecpp.h>
66 :
67 : #include "xml.h"
68 :
69 : #ifdef HAVE_RULES
70 : #ifdef _WIN32
71 : #define PCRE_STATIC
72 : #endif
73 : #include <pcre.h>
74 : #endif
75 :
76 : class SymbolDatabase;
77 :
78 : static constexpr char Version[] = CPPCHECK_VERSION_STRING;
79 : static constexpr char ExtraVersion[] = "";
80 :
81 : static constexpr char FILELIST[] = "cppcheck-addon-ctu-file-list";
82 :
83 : static TimerResults s_timerResults;
84 :
85 : // CWE ids used
86 : static const CWE CWE398(398U); // Indicator of Poor Code Quality
87 :
88 : // File deleter
89 : namespace {
90 : class FilesDeleter {
91 : public:
92 2355 : FilesDeleter() = default;
93 2355 : ~FilesDeleter() {
94 2355 : for (const std::string& fileName: mFilenames)
95 0 : std::remove(fileName.c_str());
96 2355 : }
97 0 : void addFile(const std::string& fileName) {
98 0 : mFilenames.push_back(fileName);
99 0 : }
100 : private:
101 : std::vector<std::string> mFilenames;
102 : };
103 : }
104 :
105 0 : static std::string cmdFileName(std::string f)
106 : {
107 0 : f = Path::toNativeSeparators(std::move(f));
108 0 : if (f.find(' ') != std::string::npos)
109 0 : return "\"" + f + "\"";
110 0 : return f;
111 : }
112 :
113 4 : static std::vector<std::string> split(const std::string &str, const std::string &sep=" ")
114 : {
115 4 : std::vector<std::string> ret;
116 12 : for (std::string::size_type startPos = 0U; startPos < str.size();) {
117 10 : startPos = str.find_first_not_of(sep, startPos);
118 10 : if (startPos == std::string::npos)
119 2 : break;
120 :
121 8 : if (str[startPos] == '\"') {
122 2 : const std::string::size_type endPos = str.find('\"', startPos + 1);
123 2 : ret.push_back(str.substr(startPos + 1, endPos - startPos - 1));
124 2 : startPos = (endPos < str.size()) ? (endPos + 1) : endPos;
125 2 : continue;
126 : }
127 :
128 6 : const std::string::size_type endPos = str.find(sep, startPos + 1);
129 6 : ret.push_back(str.substr(startPos, endPos - startPos));
130 6 : startPos = endPos;
131 : }
132 :
133 4 : return ret;
134 : }
135 :
136 25 : static std::string getDumpFileName(const Settings& settings, const std::string& filename)
137 : {
138 25 : if (!settings.dumpFile.empty())
139 0 : return settings.dumpFile;
140 :
141 50 : std::string extension;
142 25 : if (settings.dump)
143 0 : extension = ".dump";
144 : else
145 25 : extension = "." + std::to_string(settings.pid) + ".dump";
146 :
147 25 : if (!settings.dump && !settings.buildDir.empty())
148 0 : return AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, filename, emptyString) + extension;
149 25 : return filename + extension;
150 : }
151 :
152 25 : static std::string getCtuInfoFileName(const std::string &dumpFile)
153 : {
154 50 : return dumpFile.substr(0, dumpFile.size()-4) + "ctu-info";
155 : }
156 :
157 2355 : static void createDumpFile(const Settings& settings,
158 : const std::string& filename,
159 : std::ofstream& fdump,
160 : std::string& dumpFile)
161 : {
162 2355 : if (!settings.dump && settings.addons.empty())
163 2352 : return;
164 0 : dumpFile = getDumpFileName(settings, filename);
165 :
166 0 : fdump.open(dumpFile);
167 0 : if (!fdump.is_open())
168 0 : return;
169 :
170 : {
171 0 : std::ofstream fout(getCtuInfoFileName(dumpFile));
172 : }
173 :
174 0 : std::string language;
175 0 : switch (settings.enforcedLang) {
176 0 : case Standards::Language::C:
177 0 : language = " language=\"c\"";
178 0 : break;
179 0 : case Standards::Language::CPP:
180 0 : language = " language=\"cpp\"";
181 0 : break;
182 0 : case Standards::Language::None:
183 : {
184 : // TODO: error out on unknown language?
185 0 : const Standards::Language lang = Path::identify(filename);
186 0 : if (lang == Standards::Language::CPP)
187 0 : language = " language=\"cpp\"";
188 0 : else if (lang == Standards::Language::C)
189 0 : language = " language=\"c\"";
190 0 : break;
191 : }
192 : }
193 :
194 0 : fdump << "<?xml version=\"1.0\"?>\n";
195 0 : fdump << "<dumps" << language << ">\n";
196 : fdump << " <platform"
197 : << " name=\"" << settings.platform.toString() << '\"'
198 0 : << " char_bit=\"" << settings.platform.char_bit << '\"'
199 0 : << " short_bit=\"" << settings.platform.short_bit << '\"'
200 0 : << " int_bit=\"" << settings.platform.int_bit << '\"'
201 0 : << " long_bit=\"" << settings.platform.long_bit << '\"'
202 0 : << " long_long_bit=\"" << settings.platform.long_long_bit << '\"'
203 0 : << " pointer_bit=\"" << (settings.platform.sizeof_pointer * settings.platform.char_bit) << '\"'
204 0 : << "/>" << '\n';
205 : }
206 :
207 0 : static std::string detectPython(const CppCheck::ExecuteCmdFn &executeCommand)
208 : {
209 : #ifdef _WIN32
210 : const char *py_exes[] = { "python3.exe", "python.exe" };
211 : #else
212 0 : const char *py_exes[] = { "python3", "python" };
213 : #endif
214 0 : for (const char* py_exe : py_exes) {
215 0 : std::string out;
216 : #ifdef _MSC_VER
217 : // FIXME: hack to avoid debug assertion with _popen() in executeCommand() for non-existing commands
218 : const std::string cmd = std::string(py_exe) + " --version >NUL 2>&1";
219 : if (system(cmd.c_str()) != 0) {
220 : // TODO: get more detailed error?
221 : continue;
222 : }
223 : #endif
224 0 : if (executeCommand(py_exe, split("--version"), "2>&1", out) == EXIT_SUCCESS && startsWith(out, "Python ") && std::isdigit(out[7])) {
225 0 : return py_exe;
226 : }
227 : }
228 0 : return "";
229 : }
230 :
231 0 : static std::vector<picojson::value> executeAddon(const AddonInfo &addonInfo,
232 : const std::string &defaultPythonExe,
233 : const std::string &file,
234 : const std::string &premiumArgs,
235 : const CppCheck::ExecuteCmdFn &executeCommand)
236 : {
237 0 : const std::string redirect = "2>&1";
238 :
239 0 : std::string pythonExe;
240 :
241 0 : if (!addonInfo.executable.empty())
242 0 : pythonExe = addonInfo.executable;
243 0 : else if (!addonInfo.python.empty())
244 0 : pythonExe = cmdFileName(addonInfo.python);
245 0 : else if (!defaultPythonExe.empty())
246 0 : pythonExe = cmdFileName(defaultPythonExe);
247 : else {
248 : // store in static variable so we only look this up once
249 0 : static const std::string detectedPythonExe = detectPython(executeCommand);
250 0 : if (detectedPythonExe.empty())
251 0 : throw InternalError(nullptr, "Failed to auto detect python");
252 0 : pythonExe = detectedPythonExe;
253 : }
254 :
255 0 : std::string args;
256 0 : if (addonInfo.executable.empty())
257 0 : args = cmdFileName(addonInfo.runScript) + " " + cmdFileName(addonInfo.scriptFile);
258 0 : args += std::string(args.empty() ? "" : " ") + "--cli" + addonInfo.args;
259 0 : if (!premiumArgs.empty() && !addonInfo.executable.empty())
260 0 : args += " " + premiumArgs;
261 :
262 0 : const bool is_file_list = (file.find(FILELIST) != std::string::npos);
263 0 : const std::string fileArg = (is_file_list ? " --file-list " : " ") + cmdFileName(file);
264 0 : args += fileArg;
265 :
266 0 : std::string result;
267 0 : if (const int exitcode = executeCommand(pythonExe, split(args), redirect, result)) {
268 0 : std::string message("Failed to execute addon '" + addonInfo.name + "' - exitcode is " + std::to_string(exitcode));
269 0 : std::string details = pythonExe + " " + args;
270 0 : if (result.size() > 2) {
271 0 : details += "\nOutput:\n";
272 0 : details += result;
273 0 : const auto pos = details.find_last_not_of("\n\r");
274 0 : if (pos != std::string::npos)
275 0 : details.resize(pos + 1);
276 : }
277 0 : throw InternalError(nullptr, std::move(message), std::move(details));
278 : }
279 :
280 0 : std::vector<picojson::value> addonResult;
281 :
282 : // Validate output..
283 0 : std::istringstream istr(result);
284 0 : std::string line;
285 0 : while (std::getline(istr, line)) {
286 : // TODO: also bail out?
287 0 : if (line.empty()) {
288 : //std::cout << "addon '" << addonInfo.name << "' result contains empty line" << std::endl;
289 0 : continue;
290 : }
291 :
292 : // TODO: get rid of this
293 0 : if (startsWith(line,"Checking ")) {
294 : //std::cout << "addon '" << addonInfo.name << "' result contains 'Checking ' line" << std::endl;
295 0 : continue;
296 : }
297 :
298 0 : if (line[0] != '{') {
299 : //std::cout << "addon '" << addonInfo.name << "' result is not a JSON" << std::endl;
300 :
301 0 : result.erase(result.find_last_not_of('\n') + 1, std::string::npos); // Remove trailing newlines
302 0 : throw InternalError(nullptr, "Failed to execute '" + pythonExe + " " + args + "'. " + result);
303 : }
304 :
305 : //std::cout << "addon '" << addonInfo.name << "' result is " << line << std::endl;
306 :
307 : // TODO: make these failures?
308 0 : picojson::value res;
309 0 : const std::string err = picojson::parse(res, line);
310 0 : if (!err.empty()) {
311 : //std::cout << "addon '" << addonInfo.name << "' result is not a valid JSON (" << err << ")" << std::endl;
312 0 : continue;
313 : }
314 0 : if (!res.is<picojson::object>()) {
315 : //std::cout << "addon '" << addonInfo.name << "' result is not a JSON object" << std::endl;
316 0 : continue;
317 : }
318 0 : addonResult.emplace_back(std::move(res));
319 : }
320 :
321 : // Valid results
322 0 : return addonResult;
323 : }
324 :
325 2 : static std::string getDefinesFlags(const std::string &semicolonSeparatedString)
326 : {
327 2 : std::string flags;
328 2 : for (const std::string &d: split(semicolonSeparatedString, ";"))
329 0 : flags += "-D" + d + " ";
330 2 : return flags;
331 : }
332 :
333 2853 : CppCheck::CppCheck(ErrorLogger &errorLogger,
334 : bool useGlobalSuppressions,
335 2853 : ExecuteCmdFn executeCommand)
336 : : mErrorLogger(errorLogger)
337 : , mUseGlobalSuppressions(useGlobalSuppressions)
338 2853 : , mExecuteCommand(std::move(executeCommand))
339 2854 : {}
340 :
341 4134 : CppCheck::~CppCheck()
342 : {
343 2931 : while (!mFileInfo.empty()) {
344 864 : delete mFileInfo.back();
345 864 : mFileInfo.pop_back();
346 : }
347 :
348 2066 : if (mPlistFile.is_open()) {
349 401 : mPlistFile << ErrorLogger::plistFooter();
350 401 : mPlistFile.close();
351 : }
352 2067 : }
353 :
354 615 : const char * CppCheck::version()
355 : {
356 615 : return Version;
357 : }
358 :
359 5 : const char * CppCheck::extraVersion()
360 : {
361 5 : return ExtraVersion;
362 : }
363 :
364 0 : static bool reportClangErrors(std::istream &is, const std::function<void(const ErrorMessage&)>& reportErr, std::vector<ErrorMessage> &warnings)
365 : {
366 0 : std::string line;
367 0 : while (std::getline(is, line)) {
368 0 : if (line.empty() || line[0] == ' ' || line[0] == '`' || line[0] == '-')
369 0 : continue;
370 :
371 0 : std::string::size_type pos3 = line.find(": error: ");
372 0 : if (pos3 == std::string::npos)
373 0 : pos3 = line.find(": fatal error:");
374 0 : if (pos3 == std::string::npos)
375 0 : pos3 = line.find(": warning:");
376 0 : if (pos3 == std::string::npos)
377 0 : continue;
378 :
379 : // file:line:column: error: ....
380 0 : const std::string::size_type pos2 = line.rfind(':', pos3 - 1);
381 0 : const std::string::size_type pos1 = line.rfind(':', pos2 - 1);
382 :
383 0 : if (pos1 >= pos2 || pos2 >= pos3)
384 0 : continue;
385 :
386 0 : const std::string filename = line.substr(0, pos1);
387 0 : const std::string linenr = line.substr(pos1+1, pos2-pos1-1);
388 0 : const std::string colnr = line.substr(pos2+1, pos3-pos2-1);
389 0 : const std::string msg = line.substr(line.find(':', pos3+1) + 2);
390 :
391 0 : const std::string locFile = Path::toNativeSeparators(filename);
392 0 : const int line_i = strToInt<int>(linenr);
393 0 : const int column = strToInt<unsigned int>(colnr);
394 0 : ErrorMessage::FileLocation loc(locFile, line_i, column);
395 0 : ErrorMessage errmsg({std::move(loc)},
396 : locFile,
397 : Severity::error,
398 : msg,
399 : "syntaxError",
400 0 : Certainty::normal);
401 :
402 0 : if (line.compare(pos3, 10, ": warning:") == 0) {
403 0 : warnings.push_back(std::move(errmsg));
404 0 : continue;
405 : }
406 :
407 0 : reportErr(errmsg);
408 :
409 0 : return true;
410 : }
411 0 : return false;
412 : }
413 :
414 0 : unsigned int CppCheck::checkClang(const std::string &path)
415 : {
416 0 : if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mUnusedFunctionsCheck)
417 0 : mUnusedFunctionsCheck.reset(new CheckUnusedFunctions());
418 :
419 0 : if (!mSettings.quiet)
420 0 : mErrorLogger.reportOut(std::string("Checking ") + path + " ...", Color::FgGreen);
421 :
422 : // TODO: this ignores the configured language
423 0 : const bool isCpp = Path::identify(path) == Standards::Language::CPP;
424 0 : const std::string langOpt = isCpp ? "-x c++" : "-x c";
425 0 : const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, emptyString);
426 0 : const std::string clangcmd = analyzerInfo + ".clang-cmd";
427 0 : const std::string clangStderr = analyzerInfo + ".clang-stderr";
428 0 : const std::string clangAst = analyzerInfo + ".clang-ast";
429 0 : std::string exe = mSettings.clangExecutable;
430 : #ifdef _WIN32
431 : // append .exe if it is not a path
432 : if (Path::fromNativeSeparators(mSettings.clangExecutable).find('/') == std::string::npos) {
433 : exe += ".exe";
434 : }
435 : #endif
436 :
437 0 : std::string flags(langOpt + " ");
438 : // TODO: does not apply C standard
439 0 : if (isCpp && !mSettings.standards.stdValue.empty())
440 0 : flags += "-std=" + mSettings.standards.stdValue + " ";
441 :
442 0 : for (const std::string &i: mSettings.includePaths)
443 0 : flags += "-I" + i + " ";
444 :
445 0 : flags += getDefinesFlags(mSettings.userDefines);
446 :
447 0 : const std::string args2 = "-fsyntax-only -Xclang -ast-dump -fno-color-diagnostics " + flags + path;
448 0 : const std::string redirect2 = analyzerInfo.empty() ? std::string("2>&1") : ("2> " + clangStderr);
449 0 : if (!mSettings.buildDir.empty()) {
450 0 : std::ofstream fout(clangcmd);
451 0 : fout << exe << " " << args2 << " " << redirect2 << std::endl;
452 0 : } else if (mSettings.verbose && !mSettings.quiet) {
453 0 : mErrorLogger.reportOut(exe + " " + args2);
454 : }
455 :
456 0 : std::string output2;
457 0 : const int exitcode = mExecuteCommand(exe,split(args2),redirect2,output2);
458 0 : if (exitcode != EXIT_SUCCESS) {
459 : // TODO: report as proper error
460 0 : std::cerr << "Failed to execute '" << exe << " " << args2 << " " << redirect2 << "' - (exitcode: " << exitcode << " / output: " << output2 << ")" << std::endl;
461 0 : return 0; // TODO: report as failure?
462 : }
463 :
464 0 : if (output2.find("TranslationUnitDecl") == std::string::npos) {
465 : // TODO: report as proper error
466 0 : std::cerr << "Failed to execute '" << exe << " " << args2 << " " << redirect2 << "' - (no TranslationUnitDecl in output)" << std::endl;
467 0 : return 0; // TODO: report as failure?
468 : }
469 :
470 : // Ensure there are not syntax errors...
471 0 : std::vector<ErrorMessage> compilerWarnings;
472 0 : if (!mSettings.buildDir.empty()) {
473 0 : std::ifstream fin(clangStderr);
474 0 : auto reportError = [this](const ErrorMessage& errorMessage) {
475 0 : reportErr(errorMessage);
476 0 : };
477 0 : if (reportClangErrors(fin, reportError, compilerWarnings))
478 0 : return 0;
479 : } else {
480 0 : std::istringstream istr(output2);
481 0 : auto reportError = [this](const ErrorMessage& errorMessage) {
482 0 : reportErr(errorMessage);
483 0 : };
484 0 : if (reportClangErrors(istr, reportError, compilerWarnings))
485 0 : return 0;
486 : }
487 :
488 0 : if (!mSettings.buildDir.empty()) {
489 0 : std::ofstream fout(clangAst);
490 0 : fout << output2 << std::endl;
491 : }
492 :
493 : try {
494 0 : Tokenizer tokenizer(mSettings, *this);
495 0 : tokenizer.list.appendFileIfNew(path);
496 0 : std::istringstream ast(output2);
497 0 : clangimport::parseClangAstDump(tokenizer, ast);
498 0 : ValueFlow::setValues(tokenizer.list,
499 0 : const_cast<SymbolDatabase&>(*tokenizer.getSymbolDatabase()),
500 : *this,
501 0 : mSettings,
502 : &s_timerResults);
503 0 : if (mSettings.debugnormal)
504 0 : tokenizer.printDebugOutput(1);
505 0 : checkNormalTokens(tokenizer);
506 :
507 : // create dumpfile
508 0 : std::ofstream fdump;
509 0 : std::string dumpFile;
510 0 : createDumpFile(mSettings, path, fdump, dumpFile);
511 0 : if (fdump.is_open()) {
512 : // TODO: use tinyxml2 to create XML
513 0 : fdump << "<dump cfg=\"\">\n";
514 0 : for (const ErrorMessage& errmsg: compilerWarnings)
515 0 : fdump << " <clang-warning file=\"" << toxml(errmsg.callStack.front().getfile()) << "\" line=\"" << errmsg.callStack.front().line << "\" column=\"" << errmsg.callStack.front().column << "\" message=\"" << toxml(errmsg.shortMessage()) << "\"/>\n";
516 0 : fdump << " <standards>\n";
517 0 : fdump << " <c version=\"" << mSettings.standards.getC() << "\"/>\n";
518 0 : fdump << " <cpp version=\"" << mSettings.standards.getCPP() << "\"/>\n";
519 0 : fdump << " </standards>\n";
520 0 : tokenizer.dump(fdump);
521 0 : fdump << "</dump>\n";
522 0 : fdump << "</dumps>\n";
523 0 : fdump.close();
524 : }
525 :
526 : // run addons
527 0 : executeAddons(dumpFile, path);
528 :
529 0 : } catch (const InternalError &e) {
530 0 : const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, path, "Bailing out from analysis: Processing Clang AST dump failed");
531 0 : reportErr(errmsg);
532 0 : } catch (const TerminateException &) {
533 : // Analysis is terminated
534 0 : return mExitCode;
535 0 : } catch (const std::exception &e) {
536 0 : internalError(path, std::string("Processing Clang AST dump failed: ") + e.what());
537 : }
538 :
539 0 : return mExitCode;
540 : }
541 :
542 1224 : unsigned int CppCheck::check(const std::string &path)
543 : {
544 1224 : if (mSettings.clang)
545 0 : return checkClang(Path::simplifyPath(path));
546 :
547 1224 : return checkFile(Path::simplifyPath(path), emptyString);
548 : }
549 :
550 2 : unsigned int CppCheck::check(const std::string &path, const std::string &content)
551 : {
552 2 : std::istringstream iss(content);
553 4 : return checkFile(Path::simplifyPath(path), emptyString, &iss);
554 : }
555 :
556 1138 : unsigned int CppCheck::check(const FileSettings &fs)
557 : {
558 : // TODO: move to constructor when CppCheck no longer owns the settings
559 1138 : if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mUnusedFunctionsCheck)
560 1 : mUnusedFunctionsCheck.reset(new CheckUnusedFunctions());
561 :
562 2276 : CppCheck temp(mErrorLogger, mUseGlobalSuppressions, mExecuteCommand);
563 1138 : temp.mSettings = mSettings;
564 1138 : if (!temp.mSettings.userDefines.empty())
565 0 : temp.mSettings.userDefines += ';';
566 1138 : if (mSettings.clang)
567 0 : temp.mSettings.userDefines += fs.defines;
568 : else
569 1138 : temp.mSettings.userDefines += fs.cppcheckDefines();
570 1138 : temp.mSettings.includePaths = fs.includePaths;
571 1138 : temp.mSettings.userUndefs.insert(fs.undefs.cbegin(), fs.undefs.cend());
572 1138 : if (fs.standard.find("++") != std::string::npos)
573 0 : temp.mSettings.standards.setCPP(fs.standard);
574 1138 : else if (!fs.standard.empty())
575 0 : temp.mSettings.standards.setC(fs.standard);
576 1138 : if (fs.platformType != Platform::Type::Unspecified)
577 0 : temp.mSettings.platform.set(fs.platformType);
578 1138 : if (mSettings.clang) {
579 0 : temp.mSettings.includePaths.insert(temp.mSettings.includePaths.end(), fs.systemIncludePaths.cbegin(), fs.systemIncludePaths.cend());
580 : // TODO: propagate back suppressions
581 0 : const unsigned int returnValue = temp.check(Path::simplifyPath(fs.filename()));
582 0 : if (mUnusedFunctionsCheck)
583 0 : mUnusedFunctionsCheck->updateFunctionData(*temp.mUnusedFunctionsCheck);
584 0 : return returnValue;
585 : }
586 1138 : const unsigned int returnValue = temp.checkFile(Path::simplifyPath(fs.filename()), fs.cfg);
587 1138 : mSettings.supprs.nomsg.addSuppressions(temp.mSettings.supprs.nomsg.getSuppressions());
588 1138 : if (mUnusedFunctionsCheck)
589 1 : mUnusedFunctionsCheck->updateFunctionData(*temp.mUnusedFunctionsCheck);
590 1138 : return returnValue;
591 : }
592 :
593 2357 : static simplecpp::TokenList createTokenList(const std::string& filename, std::vector<std::string>& files, simplecpp::OutputList* outputList, std::istream* fileStream)
594 : {
595 2357 : if (fileStream)
596 2 : return {*fileStream, files, filename, outputList};
597 :
598 2355 : return {filename, files, outputList};
599 : }
600 :
601 2359 : unsigned int CppCheck::checkFile(const std::string& filename, const std::string &cfgname, std::istream* fileStream)
602 : {
603 : // TODO: move to constructor when CppCheck no longer owns the settings
604 2359 : if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mUnusedFunctionsCheck)
605 2 : mUnusedFunctionsCheck.reset(new CheckUnusedFunctions());
606 :
607 2364 : mExitCode = 0;
608 :
609 2364 : if (Settings::terminated())
610 0 : return mExitCode;
611 :
612 4727 : const Timer fileTotalTimer(mSettings.showtime == SHOWTIME_MODES::SHOWTIME_FILE_TOTAL, filename);
613 :
614 2364 : if (!mSettings.quiet) {
615 466 : std::string fixedpath = Path::simplifyPath(filename);
616 233 : fixedpath = Path::toNativeSeparators(std::move(fixedpath));
617 233 : mErrorLogger.reportOut(std::string("Checking ") + fixedpath + ' ' + cfgname + std::string("..."), Color::FgGreen);
618 :
619 233 : if (mSettings.verbose) {
620 0 : mErrorLogger.reportOut("Defines:" + mSettings.userDefines);
621 0 : std::string undefs;
622 0 : for (const std::string& U : mSettings.userUndefs) {
623 0 : if (!undefs.empty())
624 0 : undefs += ';';
625 0 : undefs += ' ' + U;
626 : }
627 0 : mErrorLogger.reportOut("Undefines:" + undefs);
628 0 : std::string includePaths;
629 0 : for (const std::string &I : mSettings.includePaths)
630 0 : includePaths += " -I" + I;
631 0 : mErrorLogger.reportOut("Includes:" + includePaths);
632 0 : mErrorLogger.reportOut(std::string("Platform:") + mSettings.platform.toString());
633 : }
634 : }
635 :
636 2364 : if (mPlistFile.is_open()) {
637 99 : mPlistFile << ErrorLogger::plistFooter();
638 99 : mPlistFile.close();
639 : }
640 :
641 : try {
642 2364 : if (mSettings.library.markupFile(filename)) {
643 7 : if (mUnusedFunctionsCheck && mSettings.useSingleJob() && mSettings.buildDir.empty()) {
644 : // this is not a real source file - we just want to tokenize it. treat it as C anyways as the language needs to be determined.
645 0 : Tokenizer tokenizer(mSettings, *this);
646 0 : tokenizer.list.setLang(Standards::Language::C);
647 0 : if (fileStream) {
648 0 : tokenizer.list.createTokens(*fileStream, filename);
649 : }
650 : else {
651 0 : std::ifstream in(filename);
652 0 : tokenizer.list.createTokens(in, filename);
653 : }
654 0 : mUnusedFunctionsCheck->parseTokens(tokenizer, mSettings);
655 : }
656 9 : return EXIT_SUCCESS;
657 : }
658 :
659 2354 : simplecpp::OutputList outputList;
660 2354 : std::vector<std::string> files;
661 2353 : simplecpp::TokenList tokens1 = createTokenList(filename, files, &outputList, fileStream);
662 :
663 : // If there is a syntax error, report it and stop
664 2 : const auto output_it = std::find_if(outputList.cbegin(), outputList.cend(), [](const simplecpp::Output &output){
665 2 : return Preprocessor::hasErrors(output);
666 2357 : });
667 2355 : if (output_it != outputList.cend()) {
668 2 : const simplecpp::Output &output = *output_it;
669 4 : std::string file = Path::fromNativeSeparators(output.location.file());
670 2 : if (mSettings.relativePaths)
671 0 : file = Path::getRelativePath(file, mSettings.basePaths);
672 :
673 4 : ErrorMessage::FileLocation loc1(file, output.location.line, output.location.col);
674 :
675 2 : ErrorMessage errmsg({std::move(loc1)},
676 : "",
677 : Severity::error,
678 2 : output.msg,
679 : "syntaxError",
680 12 : Certainty::normal);
681 2 : reportErr(errmsg);
682 2 : return mExitCode;
683 : }
684 :
685 2351 : Preprocessor preprocessor(mSettings, *this);
686 :
687 2355 : if (!preprocessor.loadFiles(tokens1, files))
688 0 : return mExitCode;
689 :
690 2355 : if (!mSettings.plistOutput.empty()) {
691 1200 : std::string filename2;
692 600 : if (filename.find('/') != std::string::npos)
693 0 : filename2 = filename.substr(filename.rfind('/') + 1);
694 : else
695 600 : filename2 = filename;
696 600 : const std::size_t fileNameHash = std::hash<std::string> {}(filename);
697 600 : filename2 = mSettings.plistOutput + filename2.substr(0, filename2.find('.')) + "_" + std::to_string(fileNameHash) + ".plist";
698 600 : mPlistFile.open(filename2);
699 600 : mPlistFile << ErrorLogger::plistHeader(version(), files);
700 : }
701 :
702 2355 : std::string dumpProlog;
703 2355 : if (mSettings.dump || !mSettings.addons.empty()) {
704 0 : dumpProlog += getDumpFileContentsRawTokens(files, tokens1);
705 : }
706 :
707 : // Parse comments and then remove them
708 2354 : preprocessor.inlineSuppressions(tokens1, mSettings.supprs.nomsg);
709 2354 : if (mSettings.dump || !mSettings.addons.empty()) {
710 0 : std::ostringstream oss;
711 0 : mSettings.supprs.nomsg.dump(oss);
712 0 : dumpProlog += oss.str();
713 : }
714 2355 : tokens1.removeComments();
715 2352 : preprocessor.removeComments();
716 :
717 2354 : if (!mSettings.buildDir.empty()) {
718 : // Get toolinfo
719 0 : std::ostringstream toolinfo;
720 0 : toolinfo << CPPCHECK_VERSION_STRING;
721 0 : toolinfo << (mSettings.severity.isEnabled(Severity::warning) ? 'w' : ' ');
722 0 : toolinfo << (mSettings.severity.isEnabled(Severity::style) ? 's' : ' ');
723 0 : toolinfo << (mSettings.severity.isEnabled(Severity::performance) ? 'p' : ' ');
724 0 : toolinfo << (mSettings.severity.isEnabled(Severity::portability) ? 'p' : ' ');
725 0 : toolinfo << (mSettings.severity.isEnabled(Severity::information) ? 'i' : ' ');
726 0 : toolinfo << mSettings.userDefines;
727 0 : mSettings.supprs.nomsg.dump(toolinfo);
728 :
729 : // Calculate hash so it can be compared with old hash / future hashes
730 0 : const std::size_t hash = preprocessor.calculateHash(tokens1, toolinfo.str());
731 0 : std::list<ErrorMessage> errors;
732 0 : if (!mAnalyzerInformation.analyzeFile(mSettings.buildDir, filename, cfgname, hash, errors)) {
733 0 : while (!errors.empty()) {
734 0 : reportErr(errors.front());
735 0 : errors.pop_front();
736 : }
737 0 : return mExitCode; // known results => no need to reanalyze file
738 : }
739 : }
740 :
741 2354 : FilesDeleter filesDeleter;
742 :
743 : // write dump file xml prolog
744 2354 : std::ofstream fdump;
745 2355 : std::string dumpFile;
746 2352 : createDumpFile(mSettings, filename, fdump, dumpFile);
747 2354 : if (fdump.is_open()) {
748 0 : fdump << dumpProlog;
749 0 : if (!mSettings.dump)
750 0 : filesDeleter.addFile(dumpFile);
751 : }
752 :
753 : // Get directives
754 2355 : std::list<Directive> directives = preprocessor.createDirectives(tokens1);
755 2353 : preprocessor.simplifyPragmaAsm(&tokens1);
756 :
757 2353 : preprocessor.setPlatformInfo(&tokens1);
758 :
759 : // Get configurations..
760 2354 : std::set<std::string> configurations;
761 2353 : if ((mSettings.checkAllConfigurations && mSettings.userDefines.empty()) || mSettings.force) {
762 4708 : Timer t("Preprocessor::getConfigs", mSettings.showtime, &s_timerResults);
763 2355 : configurations = preprocessor.getConfigs(tokens1);
764 : } else {
765 0 : configurations.insert(mSettings.userDefines);
766 : }
767 :
768 2354 : if (mSettings.checkConfiguration) {
769 0 : for (const std::string &config : configurations)
770 0 : (void)preprocessor.getcode(tokens1, config, files, true);
771 :
772 0 : return 0;
773 : }
774 :
775 : #ifdef HAVE_RULES
776 : // Run define rules on raw code
777 : if (hasRule("define")) {
778 : std::string code;
779 : for (const Directive &dir : directives) {
780 : if (startsWith(dir.str,"#define ") || startsWith(dir.str,"#include "))
781 : code += "#line " + std::to_string(dir.linenr) + " \"" + dir.file + "\"\n" + dir.str + '\n';
782 : }
783 : TokenList tokenlist(&mSettings);
784 : std::istringstream istr2(code);
785 : // TODO: asserts when file has unknown extension
786 : tokenlist.createTokens(istr2, Path::identify(*files.begin())); // TODO: check result?
787 : executeRules("define", tokenlist);
788 : }
789 : #endif
790 :
791 2354 : if (!mSettings.force && configurations.size() > mSettings.maxConfigs) {
792 0 : if (mSettings.severity.isEnabled(Severity::information)) {
793 0 : tooManyConfigsError(Path::toNativeSeparators(filename),configurations.size());
794 : } else {
795 0 : mTooManyConfigs = true;
796 : }
797 : }
798 :
799 2353 : std::set<unsigned long long> hashes;
800 2354 : int checkCount = 0;
801 2354 : bool hasValidConfig = false;
802 2354 : std::list<std::string> configurationError;
803 4732 : for (const std::string &currCfg : configurations) {
804 : // bail out if terminated
805 2377 : if (Settings::terminated())
806 0 : break;
807 :
808 : // Check only a few configurations (default 12), after that bail out, unless --force
809 : // was used.
810 2377 : if (!mSettings.force && ++checkCount > mSettings.maxConfigs)
811 0 : break;
812 :
813 2377 : if (!mSettings.userDefines.empty()) {
814 0 : mCurrentConfig = mSettings.userDefines;
815 0 : const std::vector<std::string> v1(split(mSettings.userDefines, ";"));
816 0 : for (const std::string &cfg: split(currCfg, ";")) {
817 0 : if (std::find(v1.cbegin(), v1.cend(), cfg) == v1.cend()) {
818 0 : mCurrentConfig += ";" + cfg;
819 : }
820 : }
821 : } else {
822 2377 : mCurrentConfig = currCfg;
823 : }
824 :
825 2376 : if (mSettings.preprocessOnly) {
826 0 : Timer t("Preprocessor::getcode", mSettings.showtime, &s_timerResults);
827 0 : std::string codeWithoutCfg = preprocessor.getcode(tokens1, mCurrentConfig, files, true);
828 0 : t.stop();
829 :
830 0 : if (startsWith(codeWithoutCfg,"#file"))
831 0 : codeWithoutCfg.insert(0U, "//");
832 0 : std::string::size_type pos = 0;
833 0 : while ((pos = codeWithoutCfg.find("\n#file",pos)) != std::string::npos)
834 0 : codeWithoutCfg.insert(pos+1U, "//");
835 0 : pos = 0;
836 0 : while ((pos = codeWithoutCfg.find("\n#endfile",pos)) != std::string::npos)
837 0 : codeWithoutCfg.insert(pos+1U, "//");
838 0 : pos = 0;
839 0 : while ((pos = codeWithoutCfg.find(Preprocessor::macroChar,pos)) != std::string::npos)
840 0 : codeWithoutCfg[pos] = ' ';
841 0 : reportOut(codeWithoutCfg);
842 0 : continue;
843 : }
844 :
845 2376 : Tokenizer tokenizer(mSettings, *this);
846 2378 : if (mSettings.showtime != SHOWTIME_MODES::SHOWTIME_NONE)
847 660 : tokenizer.setTimerResults(&s_timerResults);
848 2378 : tokenizer.setDirectives(directives); // TODO: how to avoid repeated copies?
849 :
850 : try {
851 : // Create tokens, skip rest of iteration if failed
852 : {
853 7132 : Timer timer("Tokenizer::createTokens", mSettings.showtime, &s_timerResults);
854 4756 : simplecpp::TokenList tokensP = preprocessor.preprocess(tokens1, mCurrentConfig, files, true);
855 2378 : tokenizer.list.createTokens(std::move(tokensP));
856 : }
857 2378 : hasValidConfig = true;
858 :
859 : // locations macros
860 2378 : mLocationMacros.clear();
861 333416 : for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) {
862 331046 : if (!tok->getMacroName().empty())
863 7414 : mLocationMacros[Location(files[tok->fileIndex()], tok->linenr())].emplace(tok->getMacroName());
864 : }
865 :
866 : // If only errors are printed, print filename after the check
867 2375 : if (!mSettings.quiet && (!mCurrentConfig.empty() || checkCount > 1)) {
868 23 : std::string fixedpath = Path::simplifyPath(filename);
869 23 : fixedpath = Path::toNativeSeparators(std::move(fixedpath));
870 23 : mErrorLogger.reportOut("Checking " + fixedpath + ": " + mCurrentConfig + "...", Color::FgGreen);
871 : }
872 :
873 2375 : if (!tokenizer.tokens())
874 0 : continue;
875 :
876 : // skip rest of iteration if just checking configuration
877 2376 : if (mSettings.checkConfiguration)
878 0 : continue;
879 :
880 : #ifdef HAVE_RULES
881 : // Execute rules for "raw" code
882 : executeRules("raw", tokenizer.list);
883 : #endif
884 :
885 : // Simplify tokens into normal form, skip rest of iteration if failed
886 2376 : if (!tokenizer.simplifyTokens1(mCurrentConfig))
887 0 : continue;
888 :
889 : // dump xml if --dump
890 2366 : if ((mSettings.dump || !mSettings.addons.empty()) && fdump.is_open()) {
891 0 : fdump << "<dump cfg=\"" << ErrorLogger::toxml(mCurrentConfig) << "\">" << std::endl;
892 0 : fdump << " <standards>" << std::endl;
893 0 : fdump << " <c version=\"" << mSettings.standards.getC() << "\"/>" << std::endl;
894 0 : fdump << " <cpp version=\"" << mSettings.standards.getCPP() << "\"/>" << std::endl;
895 0 : fdump << " </standards>" << std::endl;
896 0 : preprocessor.dump(fdump);
897 0 : tokenizer.dump(fdump);
898 0 : fdump << "</dump>" << std::endl;
899 : }
900 :
901 : // Need to call this even if the hash will skip this configuration
902 2370 : mSettings.supprs.nomsg.markUnmatchedInlineSuppressionsAsChecked(tokenizer);
903 :
904 : // Skip if we already met the same simplified token list
905 2361 : if (mSettings.force || mSettings.maxConfigs > 1) {
906 2361 : const std::size_t hash = tokenizer.list.calculateHash();
907 2369 : if (hashes.find(hash) != hashes.end()) {
908 5 : if (mSettings.debugwarnings)
909 5 : purgedConfigurationMessage(filename, mCurrentConfig);
910 5 : continue;
911 : }
912 2360 : hashes.insert(hash);
913 : }
914 :
915 : // Check normal tokens
916 2365 : checkNormalTokens(tokenizer);
917 8 : } catch (const simplecpp::Output &o) {
918 : // #error etc during preprocessing
919 0 : configurationError.push_back((mCurrentConfig.empty() ? "\'\'" : mCurrentConfig) + " : [" + o.location.file() + ':' + std::to_string(o.location.line) + "] " + o.msg);
920 0 : --checkCount; // don't count invalid configurations
921 :
922 0 : if (!hasValidConfig && currCfg == *configurations.rbegin()) {
923 : // If there is no valid configuration then report error..
924 0 : std::string file = Path::fromNativeSeparators(o.location.file());
925 0 : if (mSettings.relativePaths)
926 0 : file = Path::getRelativePath(file, mSettings.basePaths);
927 :
928 0 : ErrorMessage::FileLocation loc1(file, o.location.line, o.location.col);
929 :
930 0 : ErrorMessage errmsg({std::move(loc1)},
931 : filename,
932 : Severity::error,
933 0 : o.msg,
934 : "preprocessorErrorDirective",
935 0 : Certainty::normal);
936 0 : reportErr(errmsg);
937 : }
938 0 : continue;
939 :
940 0 : } catch (const TerminateException &) {
941 : // Analysis is terminated
942 0 : return mExitCode;
943 :
944 16 : } catch (const InternalError &e) {
945 16 : ErrorMessage errmsg = ErrorMessage::fromInternalError(e, &tokenizer.list, filename);
946 8 : reportErr(errmsg);
947 : }
948 : }
949 :
950 2355 : if (!hasValidConfig && configurations.size() > 1 && mSettings.severity.isEnabled(Severity::information)) {
951 0 : std::string msg;
952 0 : msg = "This file is not analyzed. Cppcheck failed to extract a valid configuration. Use -v for more details.";
953 0 : msg += "\nThis file is not analyzed. Cppcheck failed to extract a valid configuration. The tested configurations have these preprocessor errors:";
954 0 : for (const std::string &s : configurationError)
955 0 : msg += '\n' + s;
956 :
957 0 : const std::string locFile = Path::toNativeSeparators(filename);
958 0 : ErrorMessage::FileLocation loc(locFile, 0, 0);
959 0 : ErrorMessage errmsg({std::move(loc)},
960 : locFile,
961 : Severity::information,
962 : msg,
963 : "noValidConfiguration",
964 0 : Certainty::normal);
965 0 : reportErr(errmsg);
966 : }
967 :
968 : // dumped all configs, close root </dumps> element now
969 2355 : if (fdump.is_open()) {
970 0 : fdump << "</dumps>" << std::endl;
971 0 : fdump.close();
972 : }
973 :
974 2354 : executeAddons(dumpFile, Path::simplifyPath(filename));
975 :
976 0 : } catch (const TerminateException &) {
977 : // Analysis is terminated
978 0 : return mExitCode;
979 0 : } catch (const std::runtime_error &e) {
980 0 : internalError(filename, std::string("Checking file failed: ") + e.what());
981 0 : } catch (const std::bad_alloc &) {
982 0 : internalError(filename, "Checking file failed: out of memory");
983 0 : } catch (const InternalError &e) {
984 0 : const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, filename, "Bailing out from analysis: Checking file failed");
985 0 : reportErr(errmsg);
986 : }
987 :
988 2355 : if (!mSettings.buildDir.empty()) {
989 0 : mAnalyzerInformation.close();
990 : }
991 :
992 : // In jointSuppressionReport mode, unmatched suppressions are
993 : // collected after all files are processed
994 2355 : if (!mSettings.useSingleJob() && (mSettings.severity.isEnabled(Severity::information) || mSettings.checkConfiguration)) {
995 208 : SuppressionList::reportUnmatchedSuppressions(mSettings.supprs.nomsg.getUnmatchedLocalSuppressions(filename, (bool)mUnusedFunctionsCheck), *this);
996 : }
997 :
998 2355 : mErrorList.clear();
999 :
1000 2355 : if (mSettings.showtime == SHOWTIME_MODES::SHOWTIME_FILE || mSettings.showtime == SHOWTIME_MODES::SHOWTIME_TOP5_FILE)
1001 24 : printTimerResults(mSettings.showtime);
1002 :
1003 2355 : return mExitCode;
1004 : }
1005 :
1006 : // TODO: replace with ErrorMessage::fromInternalError()
1007 0 : void CppCheck::internalError(const std::string &filename, const std::string &msg)
1008 : {
1009 0 : const std::string fullmsg("Bailing out from analysis: " + msg);
1010 :
1011 0 : ErrorMessage::FileLocation loc1(filename, 0, 0);
1012 :
1013 0 : ErrorMessage errmsg({std::move(loc1)},
1014 : emptyString,
1015 : Severity::error,
1016 : fullmsg,
1017 : "internalError",
1018 0 : Certainty::normal);
1019 :
1020 0 : mErrorLogger.reportErr(errmsg);
1021 0 : }
1022 :
1023 : //---------------------------------------------------------------------------
1024 : // CppCheck - A function that checks a normal token list
1025 : //---------------------------------------------------------------------------
1026 :
1027 2365 : void CppCheck::checkNormalTokens(const Tokenizer &tokenizer)
1028 : {
1029 2365 : CheckUnusedFunctions unusedFunctionsChecker;
1030 :
1031 : // TODO: this should actually be the behavior if only "--enable=unusedFunction" is specified - see #10648
1032 2359 : const char* unusedFunctionOnly = std::getenv("UNUSEDFUNCTION_ONLY");
1033 2365 : const bool doUnusedFunctionOnly = unusedFunctionOnly && (std::strcmp(unusedFunctionOnly, "1") == 0);
1034 :
1035 2365 : if (!doUnusedFunctionOnly) {
1036 2364 : const std::time_t maxTime = mSettings.checksMaxTime > 0 ? std::time(nullptr) + mSettings.checksMaxTime : 0;
1037 :
1038 : // call all "runChecks" in all registered Check classes
1039 : // cppcheck-suppress shadowFunction - TODO: fix this
1040 63851 : for (Check *check : Check::instances()) {
1041 61488 : if (Settings::terminated())
1042 0 : return;
1043 :
1044 61488 : if (maxTime > 0 && std::time(nullptr) > maxTime) {
1045 0 : if (mSettings.debugwarnings) {
1046 0 : ErrorMessage::FileLocation loc(tokenizer.list.getFiles()[0], 0, 0);
1047 0 : ErrorMessage errmsg({std::move(loc)},
1048 : emptyString,
1049 : Severity::debug,
1050 : "Checks maximum time exceeded",
1051 : "checksMaxTime",
1052 0 : Certainty::normal);
1053 0 : reportErr(errmsg);
1054 : }
1055 0 : return;
1056 : }
1057 :
1058 122974 : Timer timerRunChecks(check->name() + "::runChecks", mSettings.showtime, &s_timerResults);
1059 61490 : check->runChecks(tokenizer, this);
1060 : }
1061 : }
1062 :
1063 2366 : if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mSettings.buildDir.empty()) {
1064 0 : unusedFunctionsChecker.parseTokens(tokenizer, mSettings);
1065 : }
1066 2365 : if (mUnusedFunctionsCheck && mSettings.useSingleJob() && mSettings.buildDir.empty()) {
1067 2 : mUnusedFunctionsCheck->parseTokens(tokenizer, mSettings);
1068 : }
1069 :
1070 2365 : if (mSettings.clang) {
1071 : // TODO: Use CTU for Clang analysis
1072 0 : return;
1073 : }
1074 :
1075 2365 : if (mSettings.useSingleJob() || !mSettings.buildDir.empty()) {
1076 : // Analyse the tokens..
1077 :
1078 791 : if (CTU::FileInfo * const fi1 = CTU::getFileInfo(tokenizer)) {
1079 791 : if (!mSettings.buildDir.empty())
1080 0 : mAnalyzerInformation.setFileInfo("ctu", fi1->toString());
1081 791 : if (mSettings.useSingleJob())
1082 791 : mFileInfo.push_back(fi1);
1083 : else
1084 0 : delete fi1;
1085 : }
1086 :
1087 791 : if (!doUnusedFunctionOnly) {
1088 : // cppcheck-suppress shadowFunction - TODO: fix this
1089 21357 : for (const Check *check : Check::instances()) {
1090 20566 : if (Check::FileInfo * const fi = check->getFileInfo(tokenizer, mSettings)) {
1091 73 : if (!mSettings.buildDir.empty())
1092 0 : mAnalyzerInformation.setFileInfo(check->name(), fi->toString());
1093 73 : if (mSettings.useSingleJob())
1094 73 : mFileInfo.push_back(fi);
1095 : else
1096 0 : delete fi;
1097 : }
1098 : }
1099 : }
1100 : }
1101 :
1102 2365 : if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mSettings.buildDir.empty()) {
1103 0 : mAnalyzerInformation.setFileInfo("CheckUnusedFunctions", unusedFunctionsChecker.analyzerInfo());
1104 : }
1105 :
1106 : #ifdef HAVE_RULES
1107 : executeRules("normal", tokenizer.list);
1108 : #endif
1109 : }
1110 :
1111 : //---------------------------------------------------------------------------
1112 :
1113 : #ifdef HAVE_RULES
1114 : bool CppCheck::hasRule(const std::string &tokenlist) const
1115 : {
1116 : return std::any_of(mSettings.rules.cbegin(), mSettings.rules.cend(), [&](const Settings::Rule& rule) {
1117 : return rule.tokenlist == tokenlist;
1118 : });
1119 : }
1120 :
1121 : static const char * pcreErrorCodeToString(const int pcreExecRet)
1122 : {
1123 : switch (pcreExecRet) {
1124 : case PCRE_ERROR_NULL:
1125 : return "Either code or subject was passed as NULL, or ovector was NULL "
1126 : "and ovecsize was not zero (PCRE_ERROR_NULL)";
1127 : case PCRE_ERROR_BADOPTION:
1128 : return "An unrecognized bit was set in the options argument (PCRE_ERROR_BADOPTION)";
1129 : case PCRE_ERROR_BADMAGIC:
1130 : return "PCRE stores a 4-byte \"magic number\" at the start of the compiled code, "
1131 : "to catch the case when it is passed a junk pointer and to detect when a "
1132 : "pattern that was compiled in an environment of one endianness is run in "
1133 : "an environment with the other endianness. This is the error that PCRE "
1134 : "gives when the magic number is not present (PCRE_ERROR_BADMAGIC)";
1135 : case PCRE_ERROR_UNKNOWN_NODE:
1136 : return "While running the pattern match, an unknown item was encountered in the "
1137 : "compiled pattern. This error could be caused by a bug in PCRE or by "
1138 : "overwriting of the compiled pattern (PCRE_ERROR_UNKNOWN_NODE)";
1139 : case PCRE_ERROR_NOMEMORY:
1140 : return "If a pattern contains back references, but the ovector that is passed "
1141 : "to pcre_exec() is not big enough to remember the referenced substrings, "
1142 : "PCRE gets a block of memory at the start of matching to use for this purpose. "
1143 : "If the call via pcre_malloc() fails, this error is given. The memory is "
1144 : "automatically freed at the end of matching. This error is also given if "
1145 : "pcre_stack_malloc() fails in pcre_exec(). "
1146 : "This can happen only when PCRE has been compiled with "
1147 : "--disable-stack-for-recursion (PCRE_ERROR_NOMEMORY)";
1148 : case PCRE_ERROR_NOSUBSTRING:
1149 : return "This error is used by the pcre_copy_substring(), pcre_get_substring(), "
1150 : "and pcre_get_substring_list() functions (see below). "
1151 : "It is never returned by pcre_exec() (PCRE_ERROR_NOSUBSTRING)";
1152 : case PCRE_ERROR_MATCHLIMIT:
1153 : return "The backtracking limit, as specified by the match_limit field in a pcre_extra "
1154 : "structure (or defaulted) was reached. "
1155 : "See the description above (PCRE_ERROR_MATCHLIMIT)";
1156 : case PCRE_ERROR_CALLOUT:
1157 : return "This error is never generated by pcre_exec() itself. "
1158 : "It is provided for use by callout functions that want to yield a distinctive "
1159 : "error code. See the pcrecallout documentation for details (PCRE_ERROR_CALLOUT)";
1160 : case PCRE_ERROR_BADUTF8:
1161 : return "A string that contains an invalid UTF-8 byte sequence was passed as a subject, "
1162 : "and the PCRE_NO_UTF8_CHECK option was not set. If the size of the output vector "
1163 : "(ovecsize) is at least 2, the byte offset to the start of the the invalid UTF-8 "
1164 : "character is placed in the first element, and a reason code is placed in the "
1165 : "second element. The reason codes are listed in the following section. For "
1166 : "backward compatibility, if PCRE_PARTIAL_HARD is set and the problem is a truncated "
1167 : "UTF-8 character at the end of the subject (reason codes 1 to 5), "
1168 : "PCRE_ERROR_SHORTUTF8 is returned instead of PCRE_ERROR_BADUTF8";
1169 : case PCRE_ERROR_BADUTF8_OFFSET:
1170 : return "The UTF-8 byte sequence that was passed as a subject was checked and found to "
1171 : "be valid (the PCRE_NO_UTF8_CHECK option was not set), but the value of "
1172 : "startoffset did not point to the beginning of a UTF-8 character or the end of "
1173 : "the subject (PCRE_ERROR_BADUTF8_OFFSET)";
1174 : case PCRE_ERROR_PARTIAL:
1175 : return "The subject string did not match, but it did match partially. See the "
1176 : "pcrepartial documentation for details of partial matching (PCRE_ERROR_PARTIAL)";
1177 : case PCRE_ERROR_BADPARTIAL:
1178 : return "This code is no longer in use. It was formerly returned when the PCRE_PARTIAL "
1179 : "option was used with a compiled pattern containing items that were not supported "
1180 : "for partial matching. From release 8.00 onwards, there are no restrictions on "
1181 : "partial matching (PCRE_ERROR_BADPARTIAL)";
1182 : case PCRE_ERROR_INTERNAL:
1183 : return "An unexpected internal error has occurred. This error could be caused by a bug "
1184 : "in PCRE or by overwriting of the compiled pattern (PCRE_ERROR_INTERNAL)";
1185 : case PCRE_ERROR_BADCOUNT:
1186 : return "This error is given if the value of the ovecsize argument is negative "
1187 : "(PCRE_ERROR_BADCOUNT)";
1188 : case PCRE_ERROR_RECURSIONLIMIT:
1189 : return "The internal recursion limit, as specified by the match_limit_recursion "
1190 : "field in a pcre_extra structure (or defaulted) was reached. "
1191 : "See the description above (PCRE_ERROR_RECURSIONLIMIT)";
1192 : case PCRE_ERROR_DFA_UITEM:
1193 : return "PCRE_ERROR_DFA_UITEM";
1194 : case PCRE_ERROR_DFA_UCOND:
1195 : return "PCRE_ERROR_DFA_UCOND";
1196 : case PCRE_ERROR_DFA_WSSIZE:
1197 : return "PCRE_ERROR_DFA_WSSIZE";
1198 : case PCRE_ERROR_DFA_RECURSE:
1199 : return "PCRE_ERROR_DFA_RECURSE";
1200 : case PCRE_ERROR_NULLWSLIMIT:
1201 : return "PCRE_ERROR_NULLWSLIMIT";
1202 : case PCRE_ERROR_BADNEWLINE:
1203 : return "An invalid combination of PCRE_NEWLINE_xxx options was "
1204 : "given (PCRE_ERROR_BADNEWLINE)";
1205 : case PCRE_ERROR_BADOFFSET:
1206 : return "The value of startoffset was negative or greater than the length "
1207 : "of the subject, that is, the value in length (PCRE_ERROR_BADOFFSET)";
1208 : case PCRE_ERROR_SHORTUTF8:
1209 : return "This error is returned instead of PCRE_ERROR_BADUTF8 when the subject "
1210 : "string ends with a truncated UTF-8 character and the PCRE_PARTIAL_HARD option is set. "
1211 : "Information about the failure is returned as for PCRE_ERROR_BADUTF8. "
1212 : "It is in fact sufficient to detect this case, but this special error code for "
1213 : "PCRE_PARTIAL_HARD precedes the implementation of returned information; "
1214 : "it is retained for backwards compatibility (PCRE_ERROR_SHORTUTF8)";
1215 : case PCRE_ERROR_RECURSELOOP:
1216 : return "This error is returned when pcre_exec() detects a recursion loop "
1217 : "within the pattern. Specifically, it means that either the whole pattern "
1218 : "or a subpattern has been called recursively for the second time at the same "
1219 : "position in the subject string. Some simple patterns that might do this "
1220 : "are detected and faulted at compile time, but more complicated cases, "
1221 : "in particular mutual recursions between two different subpatterns, "
1222 : "cannot be detected until run time (PCRE_ERROR_RECURSELOOP)";
1223 : case PCRE_ERROR_JIT_STACKLIMIT:
1224 : return "This error is returned when a pattern that was successfully studied "
1225 : "using a JIT compile option is being matched, but the memory available "
1226 : "for the just-in-time processing stack is not large enough. See the pcrejit "
1227 : "documentation for more details (PCRE_ERROR_JIT_STACKLIMIT)";
1228 : case PCRE_ERROR_BADMODE:
1229 : return "This error is given if a pattern that was compiled by the 8-bit library "
1230 : "is passed to a 16-bit or 32-bit library function, or vice versa (PCRE_ERROR_BADMODE)";
1231 : case PCRE_ERROR_BADENDIANNESS:
1232 : return "This error is given if a pattern that was compiled and saved is reloaded on a "
1233 : "host with different endianness. The utility function pcre_pattern_to_host_byte_order() "
1234 : "can be used to convert such a pattern so that it runs on the new host (PCRE_ERROR_BADENDIANNESS)";
1235 : case PCRE_ERROR_DFA_BADRESTART:
1236 : return "PCRE_ERROR_DFA_BADRESTART";
1237 : #if PCRE_MAJOR >= 8 && PCRE_MINOR >= 32
1238 : case PCRE_ERROR_BADLENGTH:
1239 : return "This error is given if pcre_exec() is called with a negative value for the length argument (PCRE_ERROR_BADLENGTH)";
1240 : case PCRE_ERROR_JIT_BADOPTION:
1241 : return "This error is returned when a pattern that was successfully studied using a JIT compile "
1242 : "option is being matched, but the matching mode (partial or complete match) does not correspond "
1243 : "to any JIT compilation mode. When the JIT fast path function is used, this error may be "
1244 : "also given for invalid options. See the pcrejit documentation for more details (PCRE_ERROR_JIT_BADOPTION)";
1245 : #endif
1246 : }
1247 : return "";
1248 : }
1249 :
1250 : void CppCheck::executeRules(const std::string &tokenlist, const TokenList &list)
1251 : {
1252 : // There is no rule to execute
1253 : if (!hasRule(tokenlist))
1254 : return;
1255 :
1256 : // Write all tokens in a string that can be parsed by pcre
1257 : std::string str;
1258 : for (const Token *tok = list.front(); tok; tok = tok->next()) {
1259 : str += " ";
1260 : str += tok->str();
1261 : }
1262 :
1263 : for (const Settings::Rule &rule : mSettings.rules) {
1264 : if (rule.tokenlist != tokenlist)
1265 : continue;
1266 :
1267 : if (!mSettings.quiet) {
1268 : reportOut("Processing rule: " + rule.pattern, Color::FgGreen);
1269 : }
1270 :
1271 : const char *pcreCompileErrorStr = nullptr;
1272 : int erroffset = 0;
1273 : pcre * const re = pcre_compile(rule.pattern.c_str(),0,&pcreCompileErrorStr,&erroffset,nullptr);
1274 : if (!re) {
1275 : if (pcreCompileErrorStr) {
1276 : const std::string msg = "pcre_compile failed: " + std::string(pcreCompileErrorStr);
1277 : const ErrorMessage errmsg(std::list<ErrorMessage::FileLocation>(),
1278 : emptyString,
1279 : Severity::error,
1280 : msg,
1281 : "pcre_compile",
1282 : Certainty::normal);
1283 :
1284 : reportErr(errmsg);
1285 : }
1286 : continue;
1287 : }
1288 :
1289 : // Optimize the regex, but only if PCRE_CONFIG_JIT is available
1290 : #ifdef PCRE_CONFIG_JIT
1291 : const char *pcreStudyErrorStr = nullptr;
1292 : pcre_extra * const pcreExtra = pcre_study(re, PCRE_STUDY_JIT_COMPILE, &pcreStudyErrorStr);
1293 : // pcre_study() returns NULL for both errors and when it can not optimize the regex.
1294 : // The last argument is how one checks for errors.
1295 : // It is NULL if everything works, and points to an error string otherwise.
1296 : if (pcreStudyErrorStr) {
1297 : const std::string msg = "pcre_study failed: " + std::string(pcreStudyErrorStr);
1298 : const ErrorMessage errmsg(std::list<ErrorMessage::FileLocation>(),
1299 : emptyString,
1300 : Severity::error,
1301 : msg,
1302 : "pcre_study",
1303 : Certainty::normal);
1304 :
1305 : reportErr(errmsg);
1306 : // pcre_compile() worked, but pcre_study() returned an error. Free the resources allocated by pcre_compile().
1307 : pcre_free(re);
1308 : continue;
1309 : }
1310 : #else
1311 : const pcre_extra * const pcreExtra = nullptr;
1312 : #endif
1313 :
1314 : int pos = 0;
1315 : int ovector[30]= {0};
1316 : while (pos < (int)str.size()) {
1317 : const int pcreExecRet = pcre_exec(re, pcreExtra, str.c_str(), (int)str.size(), pos, 0, ovector, 30);
1318 : if (pcreExecRet < 0) {
1319 : const std::string errorMessage = pcreErrorCodeToString(pcreExecRet);
1320 : if (!errorMessage.empty()) {
1321 : const ErrorMessage errmsg(std::list<ErrorMessage::FileLocation>(),
1322 : emptyString,
1323 : Severity::error,
1324 : std::string("pcre_exec failed: ") + errorMessage,
1325 : "pcre_exec",
1326 : Certainty::normal);
1327 :
1328 : reportErr(errmsg);
1329 : }
1330 : break;
1331 : }
1332 : const auto pos1 = (unsigned int)ovector[0];
1333 : const auto pos2 = (unsigned int)ovector[1];
1334 :
1335 : // jump to the end of the match for the next pcre_exec
1336 : pos = (int)pos2;
1337 :
1338 : // determine location..
1339 : int fileIndex = 0;
1340 : int line = 0;
1341 :
1342 : std::size_t len = 0;
1343 : for (const Token *tok = list.front(); tok; tok = tok->next()) {
1344 : len = len + 1U + tok->str().size();
1345 : if (len > pos1) {
1346 : fileIndex = tok->fileIndex();
1347 : line = tok->linenr();
1348 : break;
1349 : }
1350 : }
1351 :
1352 : const std::string& file = list.getFiles()[fileIndex];
1353 :
1354 : ErrorMessage::FileLocation loc(file, line, 0);
1355 :
1356 : // Create error message
1357 : const ErrorMessage errmsg({std::move(loc)},
1358 : list.getSourceFilePath(),
1359 : rule.severity,
1360 : !rule.summary.empty() ? rule.summary : "found '" + str.substr(pos1, pos2 - pos1) + "'",
1361 : rule.id,
1362 : Certainty::normal);
1363 :
1364 : // Report error
1365 : reportErr(errmsg);
1366 : }
1367 :
1368 : pcre_free(re);
1369 : #ifdef PCRE_CONFIG_JIT
1370 : // Free up the EXTRA PCRE value (may be NULL at this point)
1371 : if (pcreExtra) {
1372 : pcre_free_study(pcreExtra);
1373 : }
1374 : #endif
1375 : }
1376 : }
1377 : #endif
1378 :
1379 2355 : void CppCheck::executeAddons(const std::string& dumpFile, const std::string& file0)
1380 : {
1381 2355 : if (!dumpFile.empty()) {
1382 0 : std::vector<std::string> f{dumpFile};
1383 0 : executeAddons(f, file0);
1384 : }
1385 2355 : }
1386 :
1387 0 : void CppCheck::executeAddons(const std::vector<std::string>& files, const std::string& file0)
1388 : {
1389 0 : if (mSettings.addons.empty() || files.empty())
1390 0 : return;
1391 :
1392 0 : FilesDeleter filesDeleter;
1393 :
1394 0 : std::string fileList;
1395 :
1396 0 : if (files.size() >= 2 || endsWith(files[0], ".ctu-info")) {
1397 0 : fileList = Path::getPathFromFilename(files[0]) + FILELIST + std::to_string(mSettings.pid);
1398 0 : filesDeleter.addFile(fileList);
1399 0 : std::ofstream fout(fileList);
1400 0 : for (const std::string& f: files)
1401 0 : fout << f << std::endl;
1402 : }
1403 :
1404 : // ensure all addons have already been resolved - TODO: remove when settings are const after creation
1405 0 : assert(mSettings.addonInfos.size() == mSettings.addons.size());
1406 :
1407 0 : for (const AddonInfo &addonInfo : mSettings.addonInfos) {
1408 0 : if (addonInfo.name != "misra" && !addonInfo.ctu && endsWith(files.back(), ".ctu-info"))
1409 0 : continue;
1410 :
1411 : const std::vector<picojson::value> results =
1412 0 : executeAddon(addonInfo, mSettings.addonPython, fileList.empty() ? files[0] : fileList, mSettings.premiumArgs, mExecuteCommand);
1413 :
1414 0 : const bool misraC2023 = mSettings.premiumArgs.find("--misra-c-2023") != std::string::npos;
1415 :
1416 0 : for (const picojson::value& res : results) {
1417 : // TODO: get rid of copy?
1418 : // this is a copy so we can access missing fields and get a default value
1419 0 : picojson::object obj = res.get<picojson::object>();
1420 :
1421 0 : ErrorMessage errmsg;
1422 :
1423 0 : if (obj.count("file") > 0) {
1424 0 : std::string fileName = obj["file"].get<std::string>();
1425 0 : const int64_t lineNumber = obj["linenr"].get<int64_t>();
1426 0 : const int64_t column = obj["column"].get<int64_t>();
1427 0 : errmsg.callStack.emplace_back(std::move(fileName), lineNumber, column);
1428 0 : } else if (obj.count("loc") > 0) {
1429 0 : for (const picojson::value &locvalue: obj["loc"].get<picojson::array>()) {
1430 0 : picojson::object loc = locvalue.get<picojson::object>();
1431 0 : std::string fileName = loc["file"].get<std::string>();
1432 0 : const int64_t lineNumber = loc["linenr"].get<int64_t>();
1433 0 : const int64_t column = loc["column"].get<int64_t>();
1434 0 : std::string info = loc["info"].get<std::string>();
1435 0 : errmsg.callStack.emplace_back(std::move(fileName), std::move(info), lineNumber, column);
1436 : }
1437 : }
1438 :
1439 0 : errmsg.id = obj["addon"].get<std::string>() + "-" + obj["errorId"].get<std::string>();
1440 0 : if (misraC2023 && startsWith(errmsg.id, "misra-c2012-"))
1441 0 : errmsg.id = "misra-c2023-" + errmsg.id.substr(12);
1442 0 : errmsg.setmsg(mSettings.getMisraRuleText(errmsg.id, obj["message"].get<std::string>()));
1443 0 : const std::string severity = obj["severity"].get<std::string>();
1444 0 : errmsg.severity = severityFromString(severity);
1445 0 : if (errmsg.severity == Severity::none || errmsg.severity == Severity::internal) {
1446 0 : if (!endsWith(errmsg.id, "-logChecker"))
1447 0 : continue;
1448 0 : errmsg.severity = Severity::internal;
1449 : }
1450 0 : else if (!mSettings.severity.isEnabled(errmsg.severity)) {
1451 : // Do not filter out premium misra/cert/autosar messages that has been
1452 : // explicitly enabled with a --premium option
1453 0 : if (!isPremiumCodingStandardId(errmsg.id))
1454 0 : continue;
1455 : }
1456 0 : errmsg.file0 = file0;
1457 :
1458 0 : reportErr(errmsg);
1459 : }
1460 : }
1461 : }
1462 :
1463 25 : void CppCheck::executeAddonsWholeProgram(const std::list<FileWithDetails> &files)
1464 : {
1465 25 : if (mSettings.addons.empty())
1466 25 : return;
1467 :
1468 0 : std::vector<std::string> ctuInfoFiles;
1469 0 : for (const auto &f: files) {
1470 0 : const std::string &dumpFileName = getDumpFileName(mSettings, f.path());
1471 0 : ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName));
1472 : }
1473 :
1474 : try {
1475 0 : executeAddons(ctuInfoFiles, "");
1476 0 : } catch (const InternalError& e) {
1477 0 : const ErrorMessage errmsg = ErrorMessage::fromInternalError(e, nullptr, "", "Bailing out from analysis: Whole program analysis failed");
1478 0 : reportErr(errmsg);
1479 : }
1480 :
1481 0 : if (mSettings.buildDir.empty()) {
1482 0 : for (const std::string &f: ctuInfoFiles)
1483 0 : std::remove(f.c_str());
1484 : }
1485 : }
1486 :
1487 2527 : Settings &CppCheck::settings()
1488 : {
1489 2527 : return mSettings;
1490 : }
1491 :
1492 29 : void CppCheck::tooManyConfigsError(const std::string &file, const int numberOfConfigurations)
1493 : {
1494 29 : if (!mSettings.severity.isEnabled(Severity::information) && !mTooManyConfigs)
1495 25 : return;
1496 :
1497 29 : mTooManyConfigs = false;
1498 :
1499 29 : if (mSettings.severity.isEnabled(Severity::information) && file.empty())
1500 25 : return;
1501 :
1502 8 : std::list<ErrorMessage::FileLocation> loclist;
1503 4 : if (!file.empty()) {
1504 0 : loclist.emplace_back(file, 0, 0);
1505 : }
1506 :
1507 8 : std::ostringstream msg;
1508 4 : msg << "Too many #ifdef configurations - cppcheck only checks " << mSettings.maxConfigs;
1509 4 : if (numberOfConfigurations > mSettings.maxConfigs)
1510 0 : msg << " of " << numberOfConfigurations << " configurations. Use --force to check all configurations.\n";
1511 4 : if (file.empty())
1512 4 : msg << " configurations. Use --force to check all configurations. For more details, use --enable=information.\n";
1513 : msg << "The checking of the file will be interrupted because there are too many "
1514 : "#ifdef configurations. Checking of all #ifdef configurations can be forced "
1515 : "by --force command line option or from GUI preferences. However that may "
1516 4 : "increase the checking time.";
1517 4 : if (file.empty())
1518 4 : msg << " For more details, use --enable=information.";
1519 :
1520 :
1521 4 : ErrorMessage errmsg(std::move(loclist),
1522 : emptyString,
1523 : Severity::information,
1524 8 : msg.str(),
1525 : "toomanyconfigs", CWE398,
1526 24 : Certainty::normal);
1527 :
1528 4 : reportErr(errmsg);
1529 : }
1530 :
1531 9 : void CppCheck::purgedConfigurationMessage(const std::string &file, const std::string& configuration)
1532 : {
1533 9 : mTooManyConfigs = false;
1534 :
1535 9 : if (mSettings.severity.isEnabled(Severity::information) && file.empty())
1536 0 : return;
1537 :
1538 18 : std::list<ErrorMessage::FileLocation> loclist;
1539 9 : if (!file.empty()) {
1540 5 : loclist.emplace_back(file, 0, 0);
1541 : }
1542 :
1543 9 : ErrorMessage errmsg(std::move(loclist),
1544 : emptyString,
1545 : Severity::information,
1546 18 : "The configuration '" + configuration + "' was not checked because its code equals another one.",
1547 : "purgedConfiguration",
1548 54 : Certainty::normal);
1549 :
1550 9 : reportErr(errmsg);
1551 : }
1552 :
1553 : //---------------------------------------------------------------------------
1554 :
1555 : // TODO: part of this logic is duplicated in Executor::hasToLog()
1556 154951 : void CppCheck::reportErr(const ErrorMessage &msg)
1557 : {
1558 154951 : if (msg.severity == Severity::internal) {
1559 136567 : mErrorLogger.reportErr(msg);
1560 152862 : return;
1561 : }
1562 :
1563 18384 : if (!mSettings.library.reportErrors(msg.file0))
1564 0 : return;
1565 :
1566 18382 : std::set<std::string> macroNames;
1567 18382 : if (!msg.callStack.empty()) {
1568 36748 : const std::string &file = msg.callStack.back().getfile(false);
1569 18374 : int lineNumber = msg.callStack.back().line;
1570 18374 : const auto it = mLocationMacros.find(Location(file, lineNumber));
1571 18374 : if (it != mLocationMacros.cend())
1572 732 : macroNames = it->second;
1573 : }
1574 :
1575 : // TODO: only convert if necessary
1576 18381 : const auto errorMessage = SuppressionList::ErrorMessage::fromErrorMessage(msg, macroNames);
1577 :
1578 18382 : if (mSettings.supprs.nomsg.isSuppressed(errorMessage, mUseGlobalSuppressions)) {
1579 : // Safety: Report critical errors to ErrorLogger
1580 16284 : if (mSettings.safety && ErrorLogger::isCriticalErrorId(msg.id)) {
1581 0 : mExitCode = 1;
1582 :
1583 0 : if (mSettings.supprs.nomsg.isSuppressedExplicitly(errorMessage, mUseGlobalSuppressions)) {
1584 : // Report with internal severity to signal that there is this critical error but
1585 : // it is suppressed
1586 0 : ErrorMessage temp(msg);
1587 0 : temp.severity = Severity::internal;
1588 0 : mErrorLogger.reportErr(temp);
1589 : } else {
1590 : // Report critical error that is not explicitly suppressed
1591 0 : mErrorLogger.reportErr(msg);
1592 : }
1593 : }
1594 16284 : return;
1595 : }
1596 :
1597 : // TODO: there should be no need for the verbose and default messages here
1598 2098 : std::string errmsg = msg.toString(mSettings.verbose);
1599 2098 : if (errmsg.empty())
1600 0 : return;
1601 :
1602 : // Alert only about unique errors.
1603 : // This makes sure the errors of a single check() call are unique.
1604 : // TODO: get rid of this? This is forwarded to another ErrorLogger which is also doing this
1605 2098 : if (!mErrorList.emplace(std::move(errmsg)).second)
1606 0 : return;
1607 :
1608 2098 : if (!mSettings.buildDir.empty())
1609 0 : mAnalyzerInformation.reportErr(msg);
1610 :
1611 2098 : if (!mSettings.supprs.nofail.isSuppressed(errorMessage) && !mSettings.supprs.nomsg.isSuppressed(errorMessage)) {
1612 2088 : mExitCode = 1;
1613 : }
1614 :
1615 2097 : mErrorLogger.reportErr(msg);
1616 : // check if plistOutput should be populated and the current output file is open and the error is not suppressed
1617 2098 : if (!mSettings.plistOutput.empty() && mPlistFile.is_open() && !mSettings.supprs.nomsg.isSuppressed(errorMessage)) {
1618 : // add error to plist output file
1619 600 : mPlistFile << ErrorLogger::plistData(msg);
1620 : }
1621 : }
1622 :
1623 0 : void CppCheck::reportOut(const std::string &outmsg, Color c)
1624 : {
1625 0 : mErrorLogger.reportOut(outmsg, c);
1626 0 : }
1627 :
1628 819043 : void CppCheck::reportProgress(const std::string &filename, const char stage[], const std::size_t value)
1629 : {
1630 819043 : mErrorLogger.reportProgress(filename, stage, value);
1631 819042 : }
1632 :
1633 4 : void CppCheck::getErrorMessages(ErrorLogger &errorlogger)
1634 : {
1635 8 : Settings s;
1636 4 : s.severity.enable(Severity::warning);
1637 4 : s.severity.enable(Severity::style);
1638 4 : s.severity.enable(Severity::portability);
1639 4 : s.severity.enable(Severity::performance);
1640 4 : s.severity.enable(Severity::information);
1641 :
1642 8 : CppCheck cppcheck(errorlogger, true, nullptr);
1643 4 : cppcheck.purgedConfigurationMessage(emptyString,emptyString);
1644 4 : cppcheck.mTooManyConfigs = true;
1645 4 : cppcheck.tooManyConfigsError(emptyString,0U);
1646 : // TODO: add functions to get remaining error messages
1647 :
1648 : // call all "getErrorMessages" in all registered Check classes
1649 108 : for (std::list<Check *>::const_iterator it = Check::instances().cbegin(); it != Check::instances().cend(); ++it)
1650 104 : (*it)->getErrorMessages(&errorlogger, &s);
1651 :
1652 4 : CheckUnusedFunctions::getErrorMessages(errorlogger);
1653 4 : Preprocessor::getErrorMessages(errorlogger, s);
1654 4 : }
1655 :
1656 2 : void CppCheck::analyseClangTidy(const FileSettings &fileSettings)
1657 : {
1658 2 : std::string allIncludes;
1659 2 : for (const std::string &inc : fileSettings.includePaths) {
1660 0 : allIncludes = allIncludes + "-I\"" + inc + "\" ";
1661 : }
1662 :
1663 2 : const std::string allDefines = getDefinesFlags(fileSettings.defines);
1664 :
1665 : #ifdef _WIN32
1666 : constexpr char exe[] = "clang-tidy.exe";
1667 : #else
1668 2 : constexpr char exe[] = "clang-tidy";
1669 : #endif
1670 :
1671 4 : const std::string args = "-quiet -checks=*,-clang-analyzer-*,-llvm* \"" + fileSettings.filename() + "\" -- " + allIncludes + allDefines;
1672 2 : std::string output;
1673 2 : if (const int exitcode = mExecuteCommand(exe, split(args), emptyString, output)) {
1674 0 : std::cerr << "Failed to execute '" << exe << "' (exitcode: " << std::to_string(exitcode) << ")" << std::endl;
1675 0 : return;
1676 : }
1677 :
1678 : // parse output and create error messages
1679 4 : std::istringstream istr(output);
1680 4 : std::string line;
1681 :
1682 2 : if (!mSettings.buildDir.empty()) {
1683 0 : const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, fileSettings.filename(), emptyString);
1684 0 : std::ofstream fcmd(analyzerInfoFile + ".clang-tidy-cmd");
1685 0 : fcmd << istr.str();
1686 : }
1687 :
1688 2 : while (std::getline(istr, line)) {
1689 0 : if (line.find("error") == std::string::npos && line.find("warning") == std::string::npos)
1690 0 : continue;
1691 :
1692 0 : std::size_t endColumnPos = line.find(": error:");
1693 0 : if (endColumnPos == std::string::npos) {
1694 0 : endColumnPos = line.find(": warning:");
1695 : }
1696 :
1697 0 : const std::size_t endLinePos = line.rfind(':', endColumnPos-1);
1698 0 : const std::size_t endNamePos = line.rfind(':', endLinePos - 1);
1699 0 : const std::size_t endMsgTypePos = line.find(':', endColumnPos + 2);
1700 0 : const std::size_t endErrorPos = line.rfind('[', std::string::npos);
1701 0 : if (endLinePos==std::string::npos || endNamePos==std::string::npos || endMsgTypePos==std::string::npos || endErrorPos==std::string::npos)
1702 0 : continue;
1703 :
1704 0 : const std::string lineNumString = line.substr(endNamePos + 1, endLinePos - endNamePos - 1);
1705 0 : const std::string columnNumString = line.substr(endLinePos + 1, endColumnPos - endLinePos - 1);
1706 0 : const std::string messageString = line.substr(endMsgTypePos + 1, endErrorPos - endMsgTypePos - 1);
1707 0 : const std::string errorString = line.substr(endErrorPos, line.length());
1708 :
1709 0 : std::string fixedpath = Path::simplifyPath(line.substr(0, endNamePos));
1710 0 : const int64_t lineNumber = strToInt<int64_t>(lineNumString);
1711 0 : const int64_t column = strToInt<int64_t>(columnNumString);
1712 0 : fixedpath = Path::toNativeSeparators(std::move(fixedpath));
1713 :
1714 0 : ErrorMessage errmsg;
1715 0 : errmsg.callStack.emplace_back(fixedpath, lineNumber, column);
1716 :
1717 0 : errmsg.id = "clang-tidy-" + errorString.substr(1, errorString.length() - 2);
1718 0 : if (errmsg.id.find("performance") != std::string::npos)
1719 0 : errmsg.severity = Severity::performance;
1720 0 : else if (errmsg.id.find("portability") != std::string::npos)
1721 0 : errmsg.severity = Severity::portability;
1722 0 : else if (errmsg.id.find("cert") != std::string::npos || errmsg.id.find("misc") != std::string::npos || errmsg.id.find("unused") != std::string::npos)
1723 0 : errmsg.severity = Severity::warning;
1724 : else
1725 0 : errmsg.severity = Severity::style;
1726 :
1727 0 : errmsg.file0 = std::move(fixedpath);
1728 0 : errmsg.setmsg(messageString);
1729 0 : reportErr(errmsg);
1730 : }
1731 : }
1732 :
1733 126 : bool CppCheck::analyseWholeProgram()
1734 : {
1735 126 : bool errors = false;
1736 : // Init CTU
1737 126 : CTU::maxCtuDepth = mSettings.maxCtuDepth;
1738 : // Analyse the tokens
1739 126 : CTU::FileInfo ctu;
1740 638 : for (const Check::FileInfo *fi : mFileInfo) {
1741 512 : const auto *fi2 = dynamic_cast<const CTU::FileInfo *>(fi);
1742 512 : if (fi2) {
1743 440 : ctu.functionCalls.insert(ctu.functionCalls.end(), fi2->functionCalls.cbegin(), fi2->functionCalls.cend());
1744 440 : ctu.nestedCalls.insert(ctu.nestedCalls.end(), fi2->nestedCalls.cbegin(), fi2->nestedCalls.cend());
1745 : }
1746 : }
1747 :
1748 : // cppcheck-suppress shadowFunction - TODO: fix this
1749 3402 : for (Check *check : Check::instances())
1750 3276 : errors |= check->analyseWholeProgram(&ctu, mFileInfo, mSettings, *this); // TODO: ctu
1751 :
1752 126 : if (mUnusedFunctionsCheck)
1753 2 : errors |= mUnusedFunctionsCheck->check(mSettings, *this);
1754 :
1755 252 : return errors && (mExitCode > 0);
1756 : }
1757 :
1758 25 : unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const std::list<FileWithDetails> &files, const std::list<FileSettings>& fileSettings)
1759 : {
1760 25 : executeAddonsWholeProgram(files); // TODO: pass FileSettings
1761 25 : if (buildDir.empty()) {
1762 25 : removeCtuInfoFiles(files, fileSettings);
1763 25 : return mExitCode;
1764 : }
1765 0 : if (mSettings.checks.isEnabled(Checks::unusedFunction))
1766 0 : CheckUnusedFunctions::analyseWholeProgram(mSettings, *this, buildDir);
1767 0 : std::list<Check::FileInfo*> fileInfoList;
1768 0 : CTU::FileInfo ctuFileInfo;
1769 :
1770 : // Load all analyzer info data..
1771 0 : const std::string filesTxt(buildDir + "/files.txt");
1772 0 : std::ifstream fin(filesTxt);
1773 0 : std::string filesTxtLine;
1774 0 : while (std::getline(fin, filesTxtLine)) {
1775 0 : const std::string::size_type firstColon = filesTxtLine.find(':');
1776 0 : if (firstColon == std::string::npos)
1777 0 : continue;
1778 0 : const std::string::size_type lastColon = filesTxtLine.rfind(':');
1779 0 : if (firstColon == lastColon)
1780 0 : continue;
1781 0 : const std::string xmlfile = buildDir + '/' + filesTxtLine.substr(0,firstColon);
1782 : //const std::string sourcefile = filesTxtLine.substr(lastColon+1);
1783 :
1784 0 : tinyxml2::XMLDocument doc;
1785 0 : const tinyxml2::XMLError error = doc.LoadFile(xmlfile.c_str());
1786 0 : if (error != tinyxml2::XML_SUCCESS)
1787 0 : continue;
1788 :
1789 0 : const tinyxml2::XMLElement * const rootNode = doc.FirstChildElement();
1790 0 : if (rootNode == nullptr)
1791 0 : continue;
1792 :
1793 0 : for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) {
1794 0 : if (std::strcmp(e->Name(), "FileInfo") != 0)
1795 0 : continue;
1796 0 : const char *checkClassAttr = e->Attribute("check");
1797 0 : if (!checkClassAttr)
1798 0 : continue;
1799 0 : if (std::strcmp(checkClassAttr, "ctu") == 0) {
1800 0 : ctuFileInfo.loadFromXml(e);
1801 0 : continue;
1802 : }
1803 : // cppcheck-suppress shadowFunction - TODO: fix this
1804 0 : for (const Check *check : Check::instances()) {
1805 0 : if (checkClassAttr == check->name())
1806 0 : fileInfoList.push_back(check->loadFileInfoFromXml(e));
1807 : }
1808 : }
1809 : }
1810 :
1811 : // Set CTU max depth
1812 0 : CTU::maxCtuDepth = mSettings.maxCtuDepth;
1813 :
1814 : // Analyse the tokens
1815 : // cppcheck-suppress shadowFunction - TODO: fix this
1816 0 : for (Check *check : Check::instances())
1817 0 : check->analyseWholeProgram(&ctuFileInfo, fileInfoList, mSettings, *this);
1818 :
1819 0 : if (mUnusedFunctionsCheck)
1820 0 : mUnusedFunctionsCheck->check(mSettings, *this);
1821 :
1822 0 : for (Check::FileInfo *fi : fileInfoList)
1823 0 : delete fi;
1824 :
1825 0 : return mExitCode;
1826 : }
1827 :
1828 25 : void CppCheck::removeCtuInfoFiles(const std::list<FileWithDetails> &files, const std::list<FileSettings>& fileSettings)
1829 : {
1830 25 : if (mSettings.buildDir.empty()) {
1831 50 : for (const auto& f: files) {
1832 50 : const std::string &dumpFileName = getDumpFileName(mSettings, f.path());
1833 50 : const std::string &ctuInfoFileName = getCtuInfoFileName(dumpFileName);
1834 25 : std::remove(ctuInfoFileName.c_str());
1835 : }
1836 25 : for (const auto& fs: fileSettings) {
1837 0 : const std::string &dumpFileName = getDumpFileName(mSettings, fs.filename());
1838 0 : const std::string &ctuInfoFileName = getCtuInfoFileName(dumpFileName);
1839 0 : std::remove(ctuInfoFileName.c_str());
1840 : }
1841 : }
1842 25 : }
1843 :
1844 : // cppcheck-suppress unusedFunction - only used in tests
1845 4701 : void CppCheck::resetTimerResults()
1846 : {
1847 4701 : s_timerResults.reset();
1848 4701 : }
1849 :
1850 42 : void CppCheck::printTimerResults(SHOWTIME_MODES mode)
1851 : {
1852 42 : s_timerResults.showResults(mode);
1853 42 : }
1854 :
1855 16 : bool CppCheck::isPremiumCodingStandardId(const std::string& id) const {
1856 16 : if (mSettings.premiumArgs.find("--misra") != std::string::npos) {
1857 8 : if (startsWith(id, "misra-") || startsWith(id, "premium-misra-"))
1858 6 : return true;
1859 : }
1860 10 : if (mSettings.premiumArgs.find("--cert") != std::string::npos && startsWith(id, "premium-cert-"))
1861 1 : return true;
1862 9 : if (mSettings.premiumArgs.find("--autosar") != std::string::npos && startsWith(id, "premium-autosar-"))
1863 1 : return true;
1864 8 : return false;
1865 : }
1866 :
1867 1 : std::string CppCheck::getDumpFileContentsRawTokens(const std::vector<std::string>& files, const simplecpp::TokenList& tokens1) const {
1868 1 : std::string dumpProlog;
1869 1 : dumpProlog += " <rawtokens>\n";
1870 2 : for (unsigned int i = 0; i < files.size(); ++i) {
1871 1 : dumpProlog += " <file index=\"";
1872 1 : dumpProlog += std::to_string(i);
1873 1 : dumpProlog += "\" name=\"";
1874 1 : dumpProlog += ErrorLogger::toxml(Path::getRelativePath(files[i], mSettings.basePaths));
1875 1 : dumpProlog += "\"/>\n";
1876 : }
1877 1 : for (const simplecpp::Token *tok = tokens1.cfront(); tok; tok = tok->next) {
1878 0 : dumpProlog += " <tok ";
1879 :
1880 0 : dumpProlog += "fileIndex=\"";
1881 0 : dumpProlog += std::to_string(tok->location.fileIndex);
1882 0 : dumpProlog += "\" ";
1883 :
1884 0 : dumpProlog += "linenr=\"";
1885 0 : dumpProlog += std::to_string(tok->location.line);
1886 0 : dumpProlog += "\" ";
1887 :
1888 0 : dumpProlog +="column=\"";
1889 0 : dumpProlog += std::to_string(tok->location.col);
1890 0 : dumpProlog += "\" ";
1891 :
1892 0 : dumpProlog += "str=\"";
1893 0 : dumpProlog += ErrorLogger::toxml(tok->str());
1894 0 : dumpProlog += "\"";
1895 :
1896 0 : dumpProlog += "/>\n";
1897 : }
1898 1 : dumpProlog += " </rawtokens>\n";
1899 1 : return dumpProlog;
1900 : }
|