Cppcheck
checkthread.cpp
Go to the documentation of this file.
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 "checkthread.h"
20 
21 #include "analyzerinfo.h"
22 #include "common.h"
23 #include "cppcheck.h"
24 #include "erroritem.h"
25 #include "errorlogger.h"
26 #include "errortypes.h"
27 #include "filesettings.h"
28 #include "settings.h"
29 #include "standards.h"
30 #include "threadresult.h"
31 #include "utils.h"
32 
33 #include <algorithm>
34 #include <cstddef>
35 #include <fstream>
36 #include <iterator>
37 #include <list>
38 #include <set>
39 #include <string>
40 #include <utility>
41 #include <vector>
42 
43 #include <QByteArray>
44 #include <QChar>
45 #include <QDebug>
46 #include <QDir>
47 #include <QFile>
48 #include <QIODevice>
49 #include <QProcess>
50 #include <QRegularExpression>
51 #include <QSettings>
52 #include <QTextStream>
53 #include <QVariant>
54 
55 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
56 #include <QCharRef>
57 #endif
58 
59 // NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature
60 int CheckThread::executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output) // cppcheck-suppress passedByValue
61 {
62  output.clear();
63 
64  QStringList args2;
65  for (const std::string &arg: args)
66  args2 << QString::fromStdString(arg);
67 
68  QProcess process;
69  process.start(QString::fromStdString(exe), args2);
70  process.waitForFinished();
71 
72  if (redirect == "2>&1") {
73  QString s1 = process.readAllStandardOutput();
74  QString s2 = process.readAllStandardError();
75  output = (s1 + "\n" + s2).toStdString();
76  } else
77  output = process.readAllStandardOutput().toStdString();
78 
79  if (startsWith(redirect, "2> ")) {
80  std::ofstream fout(redirect.substr(3));
81  fout << process.readAllStandardError().toStdString();
82  }
83  return process.exitCode();
84 }
85 
86 
88  mResult(result),
89  mCppcheck(result, true, executeCommand)
90 {}
91 
92 void CheckThread::check(const Settings &settings)
93 {
94  mFiles.clear();
95  mCppcheck.settings() = settings;
96  start();
97 }
98 
99 void CheckThread::analyseWholeProgram(const QStringList &files)
100 {
101  mFiles = files;
102  mAnalyseWholeProgram = true;
103  start();
104 }
105 
106 // cppcheck-suppress unusedFunction - TODO: false positive
108 {
109  mState = Running;
110 
111  if (!mFiles.isEmpty() || mAnalyseWholeProgram) {
112  mAnalyseWholeProgram = false;
113  qDebug() << "Whole program analysis";
114  std::list<FileWithDetails> files2;
115  std::transform(mFiles.cbegin(), mFiles.cend(), std::back_inserter(files2), [&](const QString& file) {
116  return FileWithDetails{file.toStdString(), 0};
117  });
119  mFiles.clear();
120  emit done();
121  return;
122  }
123 
124  QString file = mResult.getNextFile();
125  while (!file.isEmpty() && mState == Running) {
126  qDebug() << "Checking file" << file;
127  mCppcheck.check(file.toStdString());
128  runAddonsAndTools(nullptr, file);
129  emit fileChecked(file);
130 
131  if (mState == Running)
132  file = mResult.getNextFile();
133  }
134 
135  FileSettings fileSettings = mResult.getNextFileSettings();
136  while (!fileSettings.filename().empty() && mState == Running) {
137  file = QString::fromStdString(fileSettings.filename());
138  qDebug() << "Checking file" << file;
139  mCppcheck.check(fileSettings);
140  runAddonsAndTools(&fileSettings, QString::fromStdString(fileSettings.filename()));
141  emit fileChecked(file);
142 
143  if (mState == Running)
144  fileSettings = mResult.getNextFileSettings();
145  }
146 
147  if (mState == Running)
148  mState = Ready;
149  else
150  mState = Stopped;
151 
152  emit done();
153 }
154 
155 void CheckThread::runAddonsAndTools(const FileSettings *fileSettings, const QString &fileName)
156 {
157  for (const QString& addon : mAddonsAndTools) {
158  if (addon == CLANG_ANALYZER || addon == CLANG_TIDY) {
159  if (!fileSettings)
160  continue;
161 
162  if (!fileSettings->cfg.empty() && !startsWith(fileSettings->cfg,"Debug"))
163  continue;
164 
165  QStringList args;
166  for (std::list<std::string>::const_iterator incIt = fileSettings->includePaths.cbegin(); incIt != fileSettings->includePaths.cend(); ++incIt)
167  args << ("-I" + QString::fromStdString(*incIt));
168  for (std::list<std::string>::const_iterator i = fileSettings->systemIncludePaths.cbegin(); i != fileSettings->systemIncludePaths.cend(); ++i)
169  args << "-isystem" << QString::fromStdString(*i);
170  for (const QString& def : QString::fromStdString(fileSettings->defines).split(";")) {
171  args << ("-D" + def);
172  }
173  for (const std::string& U : fileSettings->undefs) {
174  args << QString::fromStdString("-U" + U);
175  }
176 
177  const QString clangPath = CheckThread::clangTidyCmd();
178  if (!clangPath.isEmpty()) {
179  QDir dir(clangPath + "/../lib/clang");
180  for (const QString& ver : dir.entryList()) {
181  QString includePath = dir.absolutePath() + '/' + ver + "/include";
182  if (ver[0] != '.' && QDir(includePath).exists()) {
183  args << "-isystem" << includePath;
184  break;
185  }
186  }
187  }
188 
189 #ifdef Q_OS_WIN
190  // To create compile_commands.json in windows see:
191  // https://bitsmaker.gitlab.io/post/clang-tidy-from-vs2015/
192 
193  for (QString includePath : mClangIncludePaths) {
194  if (!includePath.isEmpty()) {
195  includePath.replace("\\", "/");
196  args << "-isystem" << includePath.trimmed();
197  }
198  }
199 
200  args << "-U__STDC__" << "-fno-ms-compatibility";
201 #endif
202 
203  if (!fileSettings->standard.empty())
204  args << ("-std=" + QString::fromStdString(fileSettings->standard));
205  else {
206  // TODO: pass C or C++ standard based on file type
207  const std::string std = mCppcheck.settings().standards.getCPP();
208  if (!std.empty()) {
209  args << ("-std=" + QString::fromStdString(std));
210  }
211  }
212 
213  QString analyzerInfoFile;
214 
215  const std::string &buildDir = mCppcheck.settings().buildDir;
216  if (!buildDir.empty()) {
217  analyzerInfoFile = QString::fromStdString(AnalyzerInformation::getAnalyzerInfoFile(buildDir, fileSettings->filename(), fileSettings->cfg));
218 
219  QStringList args2(args);
220  args2.insert(0,"-E");
221  args2 << fileName;
222  QProcess process;
223  process.start(clangCmd(),args2);
224  process.waitForFinished();
225  const QByteArray &ba = process.readAllStandardOutput();
226 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
227  const quint16 chksum = qChecksum(QByteArrayView(ba));
228 #else
229  const quint16 chksum = qChecksum(ba.data(), ba.length());
230 #endif
231 
232  QFile f1(analyzerInfoFile + '.' + addon + "-E");
233  if (f1.open(QIODevice::ReadOnly | QIODevice::Text)) {
234  QTextStream in1(&f1);
235  const quint16 oldchksum = in1.readAll().toInt();
236  if (oldchksum == chksum) {
237  QFile f2(analyzerInfoFile + '.' + addon + "-results");
238  if (f2.open(QIODevice::ReadOnly | QIODevice::Text)) {
239  QTextStream in2(&f2);
240  parseClangErrors(addon, fileName, in2.readAll());
241  continue;
242  }
243  }
244  f1.close();
245  }
246  f1.open(QIODevice::WriteOnly | QIODevice::Text);
247  QTextStream out1(&f1);
248  out1 << chksum;
249 
250  QFile::remove(analyzerInfoFile + '.' + addon + "-results");
251  }
252 
253  if (addon == CLANG_ANALYZER) {
254  /*
255  // Using clang
256  args.insert(0,"--analyze");
257  args.insert(1, "-Xanalyzer");
258  args.insert(2, "-analyzer-output=text");
259  args << fileName;
260  */
261  // Using clang-tidy
262  args.insert(0,"-checks=-*,clang-analyzer-*");
263  args.insert(1, fileName);
264  args.insert(2, "--");
265  } else {
266  args.insert(0,"-checks=*,-clang-analyzer-*,-llvm*");
267  args.insert(1, fileName);
268  args.insert(2, "--");
269  }
270 
271  {
272  const QString cmd(clangTidyCmd());
273  QString debug(cmd.contains(" ") ? ('\"' + cmd + '\"') : cmd);
274  for (const QString& arg : args) {
275  if (arg.contains(" "))
276  debug += " \"" + arg + '\"';
277  else
278  debug += ' ' + arg;
279  }
280  qDebug() << debug;
281 
282  if (!analyzerInfoFile.isEmpty()) {
283  QFile f(analyzerInfoFile + '.' + addon + "-cmd");
284  if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
285  QTextStream out(&f);
286  out << debug;
287  }
288  }
289  }
290 
291  QProcess process;
292  process.start(clangTidyCmd(), args);
293  process.waitForFinished(600*1000);
294  const QString errout(process.readAllStandardOutput() + "\n\n\n" + process.readAllStandardError());
295  if (!analyzerInfoFile.isEmpty()) {
296  QFile f(analyzerInfoFile + '.' + addon + "-results");
297  if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
298  QTextStream out(&f);
299  out << errout;
300  }
301  }
302 
303  parseClangErrors(addon, fileName, errout);
304  }
305  }
306 }
307 
309 {
310  mState = Stopping;
312 }
313 
314 void CheckThread::parseClangErrors(const QString &tool, const QString &file0, QString err)
315 {
316  QList<ErrorItem> errorItems;
317  ErrorItem errorItem;
318  static const QRegularExpression r1("^(.+):([0-9]+):([0-9]+): (note|warning|error|fatal error): (.*)$");
319  static const QRegularExpression r2("^(.*)\\[([a-zA-Z0-9\\-_\\.]+)\\]$");
320  QTextStream in(&err, QIODevice::ReadOnly);
321  while (!in.atEnd()) {
322  QString line = in.readLine();
323 
324  if (line.startsWith("Assertion failed:")) {
325  ErrorItem e;
326  e.errorPath.append(QErrorPathItem());
327  e.errorPath.last().file = file0;
328  e.errorPath.last().line = 1;
329  e.errorPath.last().column = 1;
330  e.errorId = tool + "-internal-error";
331  e.file0 = file0;
332  e.message = line;
334  errorItems.append(e);
335  continue;
336  }
337 
338  const QRegularExpressionMatch r1MatchRes = r1.match(line);
339  if (!r1MatchRes.hasMatch())
340  continue;
341  if (r1MatchRes.captured(4) != "note") {
342  errorItems.append(errorItem);
343  errorItem = ErrorItem();
344  errorItem.file0 = r1MatchRes.captured(1);
345  }
346 
347  errorItem.errorPath.append(QErrorPathItem());
348  errorItem.errorPath.last().file = r1MatchRes.captured(1);
349  errorItem.errorPath.last().line = r1MatchRes.captured(2).toInt();
350  errorItem.errorPath.last().column = r1MatchRes.captured(3).toInt();
351  if (r1MatchRes.captured(4) == "warning")
352  errorItem.severity = Severity::warning;
353  else if (r1MatchRes.captured(4) == "error" || r1MatchRes.captured(4) == "fatal error")
354  errorItem.severity = Severity::error;
355 
356  QString message,id;
357  const QRegularExpressionMatch r2MatchRes = r2.match(r1MatchRes.captured(5));
358  if (r2MatchRes.hasMatch()) {
359  message = r2MatchRes.captured(1);
360  const QString id1(r2MatchRes.captured(2));
361  if (id1.startsWith("clang"))
362  id = id1;
363  else
364  id = tool + '-' + r2MatchRes.captured(2);
365  if (tool == CLANG_TIDY) {
366  if (id1.startsWith("performance"))
367  errorItem.severity = Severity::performance;
368  else if (id1.startsWith("portability"))
369  errorItem.severity = Severity::portability;
370  else if (id1.startsWith("misc") && !id1.contains("unused"))
371  errorItem.severity = Severity::warning;
372  else
373  errorItem.severity = Severity::style;
374  }
375  } else {
376  message = r1MatchRes.captured(5);
377  id = CLANG_ANALYZER;
378  }
379 
380  if (errorItem.errorPath.size() == 1) {
381  errorItem.message = message;
382  errorItem.errorId = id;
383  }
384 
385  errorItem.errorPath.last().info = message;
386  }
387  errorItems.append(errorItem);
388 
389  for (const ErrorItem &e : errorItems) {
390  if (e.errorPath.isEmpty())
391  continue;
392  SuppressionList::ErrorMessage errorMessage;
393  errorMessage.setFileName(e.errorPath.back().file.toStdString());
394  errorMessage.lineNumber = e.errorPath.back().line;
395  errorMessage.errorId = e.errorId.toStdString();
396  errorMessage.symbolNames = e.symbolNames.toStdString();
397 
398  if (isSuppressed(errorMessage))
399  continue;
400 
401  std::list<ErrorMessage::FileLocation> callstack;
402  std::transform(e.errorPath.cbegin(), e.errorPath.cend(), std::back_inserter(callstack), [](const QErrorPathItem& path) {
403  return ErrorMessage::FileLocation(path.file.toStdString(), path.info.toStdString(), path.line, path.column);
404  });
405  const std::string f0 = file0.toStdString();
406  const std::string msg = e.message.toStdString();
407  const std::string id = e.errorId.toStdString();
408  ErrorMessage errmsg(callstack, f0, e.severity, msg, id, Certainty::normal);
409  mResult.reportErr(errmsg);
410  }
411 }
412 
414 {
415  return std::any_of(mSuppressions.cbegin(), mSuppressions.cend(), [&](const SuppressionList::Suppression& s) {
416  return s.isSuppressed(errorMessage);
417  });
418 }
419 
421 {
422  QString path = QSettings().value(SETTINGS_CLANG_PATH,QString()).toString();
423  if (!path.isEmpty())
424  path += '/';
425  path += "clang";
426 #ifdef Q_OS_WIN
427  path += ".exe";
428 #endif
429 
430  QProcess process;
431  process.start(path, QStringList() << "--version");
432  process.waitForFinished();
433  if (process.exitCode() == 0)
434  return path;
435 
436 #ifdef Q_OS_WIN
437  // Try to autodetect clang
438  if (QFileInfo("C:/Program Files/LLVM/bin/clang.exe").exists())
439  return "C:/Program Files/LLVM/bin/clang.exe";
440 #endif
441 
442  return QString();
443 }
444 
446 {
447  QString path = QSettings().value(SETTINGS_CLANG_PATH,QString()).toString();
448  if (!path.isEmpty())
449  path += '/';
450  path += "clang-tidy";
451 #ifdef Q_OS_WIN
452  path += ".exe";
453 #endif
454 
455  QProcess process;
456  process.start(path, QStringList() << "--version");
457  process.waitForFinished();
458  if (process.exitCode() == 0)
459  return path;
460 
461 #ifdef Q_OS_WIN
462  // Try to autodetect clang-tidy
463  if (QFileInfo("C:/Program Files/LLVM/bin/clang-tidy.exe").exists())
464  return "C:/Program Files/LLVM/bin/clang-tidy.exe";
465 #endif
466 
467  return QString();
468 }
static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg)
bool mAnalyseWholeProgram
Definition: checkthread.h:144
static QString clangTidyCmd()
Determine command to run clang-tidy.
void parseClangErrors(const QString &tool, const QString &file0, QString err)
QStringList mFiles
Definition: checkthread.h:143
@ Running
The thread is checking.
Definition: checkthread.h:119
@ Stopping
The thread will stop after current work.
Definition: checkthread.h:120
void run() override
method that is run in a thread
void analyseWholeProgram(const QStringList &files)
Run whole program analysis.
Definition: checkthread.cpp:99
CheckThread(ThreadResult &result)
Definition: checkthread.cpp:87
QStringList mAddonsAndTools
Definition: checkthread.h:145
CppCheck mCppcheck
Cppcheck itself.
Definition: checkthread.h:134
void check(const Settings &settings)
Set settings for cppcheck.
Definition: checkthread.cpp:92
void done()
cpp checking is done
static int executeCommand(std::string exe, std::vector< std::string > args, std::string redirect, std::string &output)
Definition: checkthread.cpp:60
void runAddonsAndTools(const FileSettings *fileSettings, const QString &fileName)
QStringList mClangIncludePaths
Definition: checkthread.h:146
QList< SuppressionList::Suppression > mSuppressions
Definition: checkthread.h:147
bool isSuppressed(const SuppressionList::ErrorMessage &errorMessage) const
std::atomic< State > mState
Thread's current execution state.
Definition: checkthread.h:128
static QString clangCmd()
Determine command to run clang.
ThreadResult & mResult
Definition: checkthread.h:130
bool analyseWholeProgram()
Analyse whole program, run this after all TUs has been scanned.
Definition: cppcheck.cpp:1733
Settings & settings()
Get reference to current settings.
Definition: cppcheck.cpp:1487
A class containing error data for one error.
Definition: erroritem.h:72
QString file0
Definition: erroritem.h:84
Severity severity
Definition: erroritem.h:86
QString message
Definition: erroritem.h:89
QList< QErrorPathItem > errorPath
Definition: erroritem.h:92
QString errorId
Definition: erroritem.h:85
Wrapper for error messages, provided by reportErr()
Definition: errorlogger.h:48
A class containing data for one error path item.
Definition: erroritem.h:52
This is just a container for general settings so that we don't need to pass individual values to func...
Definition: settings.h:95
static void terminate(bool t=true)
Request termination of checking.
Definition: settings.h:444
std::string buildDir
–cppcheck-build-dir.
Definition: settings.h:121
Standards standards
Struct contains standards settings.
Definition: settings.h:366
Threads use this class to obtain new files to process and to publish results.
Definition: threadresult.h:46
void reportErr(const ErrorMessage &msg) override
Information about found errors and warnings is directed here.
@ warning
Warning.
@ portability
Portability warning.
@ style
Style warning.
@ debug
Debug message.
@ information
Checking information.
@ performance
Performance warning.
@ error
Programming error.
#define SETTINGS_CLANG_PATH
Definition: common.h:82
#define CLANG_TIDY
Definition: common.h:29
#define CLANG_ANALYZER
Definition: common.h:28
File settings.
Definition: filesettings.h:57
std::string defines
Definition: filesettings.h:72
const std::string & filename() const
Definition: filesettings.h:68
std::set< std::string > undefs
Definition: filesettings.h:77
std::string cfg
Definition: filesettings.h:66
std::string standard
Definition: filesettings.h:81
std::list< std::string > includePaths
Definition: filesettings.h:78
std::list< std::string > systemIncludePaths
Definition: filesettings.h:80
std::string getCPP() const
Definition: standards.h:103
void setFileName(std::string s)
bool startsWith(const std::string &str, const char start[], std::size_t startlen)
Definition: utils.h:94