Cppcheck
cppcheckexecutor.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 "cppcheckexecutor.h"
20 
21 #include "analyzerinfo.h"
22 #include "checkersreport.h"
23 #include "cmdlinelogger.h"
24 #include "cmdlineparser.h"
25 #include "color.h"
26 #include "config.h"
27 #include "cppcheck.h"
28 #include "errorlogger.h"
29 #include "errortypes.h"
30 #include "filesettings.h"
31 #include "settings.h"
32 #include "singleexecutor.h"
33 #include "suppressions.h"
34 #include "utils.h"
35 
36 #if defined(HAS_THREADING_MODEL_THREAD)
37 #include "threadexecutor.h"
38 #endif
39 #if defined(HAS_THREADING_MODEL_FORK)
40 #include "processexecutor.h"
41 #endif
42 
43 #include <algorithm>
44 #include <cassert>
45 #include <cstdio>
46 #include <cstdlib> // EXIT_SUCCESS and EXIT_FAILURE
47 #include <ctime>
48 #include <fstream>
49 #include <iostream>
50 #include <list>
51 #include <set>
52 #include <sstream>
53 #include <unordered_set>
54 #include <utility>
55 #include <vector>
56 
57 #ifdef USE_UNIX_SIGNAL_HANDLING
58 #include "signalhandler.h"
59 #endif
60 
61 #ifdef USE_WINDOWS_SEH
62 #include "cppcheckexecutorseh.h"
63 #endif
64 
65 #ifdef _WIN32
66 #include <windows.h>
67 #endif
68 
69 #if !defined(WIN32) && !defined(__MINGW32__)
70 #include <sys/wait.h> // WIFEXITED and friends
71 #endif
72 
73 namespace {
74  class CmdLineLoggerStd : public CmdLineLogger
75  {
76  public:
77  CmdLineLoggerStd() = default;
78 
79  void printMessage(const std::string &message) override
80  {
81  printRaw("cppcheck: " + message);
82  }
83 
84  void printError(const std::string &message) override
85  {
86  printMessage("error: " + message);
87  }
88 
89  void printRaw(const std::string &message) override
90  {
91  std::cout << message << std::endl;
92  }
93  };
94 
95  class StdLogger : public ErrorLogger
96  {
97  public:
98  explicit StdLogger(const Settings& settings)
99  : mSettings(settings)
100  {
101  if (!mSettings.outputFile.empty()) {
102  mErrorOutput = new std::ofstream(settings.outputFile);
103  }
104  }
105 
106  ~StdLogger() override {
107  delete mErrorOutput;
108  }
109 
110  StdLogger(const StdLogger&) = delete;
111  StdLogger& operator=(const SingleExecutor &) = delete;
112 
113  void resetLatestProgressOutputTime() {
114  mLatestProgressOutputTime = std::time(nullptr);
115  }
116 
117  /**
118  * Helper function to print out errors. Appends a line change.
119  * @param errmsg String printed to error stream
120  */
121  void reportErr(const std::string &errmsg);
122 
123  /**
124  * @brief Write the checkers report
125  */
126  void writeCheckersReport();
127 
128  bool hasCriticalErrors() const {
129  return !mCriticalErrors.empty();
130  }
131 
132  private:
133  /**
134  * Information about progress is directed here. This should be
135  * called by the CppCheck class only.
136  *
137  * @param outmsg Progress message e.g. "Checking main.cpp..."
138  */
139  void reportOut(const std::string &outmsg, Color c = Color::Reset) override;
140 
141  /** xml output of errors */
142  void reportErr(const ErrorMessage &msg) override;
143 
144  void reportProgress(const std::string &filename, const char stage[], const std::size_t value) override;
145 
146  /**
147  * Pointer to current settings; set while check() is running for reportError().
148  */
149  const Settings& mSettings;
150 
151  /**
152  * Used to filter out duplicate error messages.
153  */
154  // TODO: store hashes instead of the full messages
155  std::unordered_set<std::string> mShownErrors;
156 
157  /**
158  * Report progress time
159  */
160  std::time_t mLatestProgressOutputTime{};
161 
162  /**
163  * Error output
164  */
165  std::ofstream* mErrorOutput{};
166 
167  /**
168  * Checkers that has been executed
169  */
170  std::set<std::string> mActiveCheckers;
171 
172  /**
173  * True if there are critical errors
174  */
175  std::string mCriticalErrors;
176  };
177 }
178 
179 // TODO: do not directly write to stdout
180 
181 #if defined(USE_WINDOWS_SEH) || defined(USE_UNIX_SIGNAL_HANDLING)
182 /*static*/ FILE* CppCheckExecutor::mExceptionOutput = stdout;
183 #endif
184 
185 int CppCheckExecutor::check(int argc, const char* const argv[])
186 {
187  Settings settings;
188  CmdLineLoggerStd logger;
189  CmdLineParser parser(logger, settings, settings.supprs);
190  if (!parser.fillSettingsFromArgs(argc, argv)) {
191  return EXIT_FAILURE;
192  }
193  if (Settings::terminated()) {
194  return EXIT_SUCCESS;
195  }
196 
197  settings.loadSummaries();
198 
199  mFiles = parser.getFiles();
200  mFileSettings = parser.getFileSettings();
201 
203 
204  const int ret = check_wrapper(settings);
205 
206  return ret;
207 }
208 
210 {
211 #ifdef USE_WINDOWS_SEH
212  if (settings.exceptionHandling)
213  return check_wrapper_seh(*this, &CppCheckExecutor::check_internal, settings);
214 #elif defined(USE_UNIX_SIGNAL_HANDLING)
215  if (settings.exceptionHandling)
216  register_signal_handler();
217 #endif
218  return check_internal(settings);
219 }
220 
221 bool CppCheckExecutor::reportSuppressions(const Settings &settings, const SuppressionList& suppressions, bool unusedFunctionCheckEnabled, const std::list<FileWithDetails> &files, const std::list<FileSettings>& fileSettings, ErrorLogger& errorLogger) {
222  const auto& suppr = suppressions.getSuppressions();
223  if (std::any_of(suppr.begin(), suppr.end(), [](const SuppressionList::Suppression& s) {
224  return s.errorId == "unmatchedSuppression" && s.fileName.empty() && s.lineNumber == SuppressionList::Suppression::NO_LINE;
225  }))
226  return false;
227 
228  bool err = false;
229  if (settings.useSingleJob()) {
230  // the two inputs may only be used exclusively
231  assert(!(!files.empty() && !fileSettings.empty()));
232 
233  for (std::list<FileWithDetails>::const_iterator i = files.cbegin(); i != files.cend(); ++i) {
235  suppressions.getUnmatchedLocalSuppressions(i->path(), unusedFunctionCheckEnabled), errorLogger);
236  }
237 
238  for (std::list<FileSettings>::const_iterator i = fileSettings.cbegin(); i != fileSettings.cend(); ++i) {
240  suppressions.getUnmatchedLocalSuppressions(i->filename(), unusedFunctionCheckEnabled), errorLogger);
241  }
242  }
243  err |= SuppressionList::reportUnmatchedSuppressions(suppressions.getUnmatchedGlobalSuppressions(unusedFunctionCheckEnabled), errorLogger);
244  return err;
245 }
246 
247 /*
248  * That is a method which gets called from check_wrapper
249  * */
250 int CppCheckExecutor::check_internal(const Settings& settings) const
251 {
252  StdLogger stdLogger(settings);
253 
254  if (settings.reportProgress >= 0)
255  stdLogger.resetLatestProgressOutputTime();
256 
257  if (settings.xml) {
258  stdLogger.reportErr(ErrorMessage::getXMLHeader(settings.cppcheckCfgProductName));
259  }
260 
261  if (!settings.buildDir.empty()) {
262  std::list<std::string> fileNames;
263  for (std::list<FileWithDetails>::const_iterator i = mFiles.cbegin(); i != mFiles.cend(); ++i)
264  fileNames.emplace_back(i->path());
266  }
267 
268  if (!settings.checkersReportFilename.empty())
269  std::remove(settings.checkersReportFilename.c_str());
270 
271  CppCheck cppcheck(stdLogger, true, executeCommand);
272  cppcheck.settings() = settings; // this is a copy
273  auto& suppressions = cppcheck.settings().supprs.nomsg;
274 
275  unsigned int returnValue = 0;
276  if (settings.useSingleJob()) {
277  // Single process
278  SingleExecutor executor(cppcheck, mFiles, mFileSettings, settings, suppressions, stdLogger);
279  returnValue = executor.check();
280  } else {
281 #if defined(HAS_THREADING_MODEL_THREAD)
282  if (settings.executor == Settings::ExecutorType::Thread) {
283  ThreadExecutor executor(mFiles, mFileSettings, settings, suppressions, stdLogger, CppCheckExecutor::executeCommand);
284  returnValue = executor.check();
285  }
286 #endif
287 #if defined(HAS_THREADING_MODEL_FORK)
288  if (settings.executor == Settings::ExecutorType::Process) {
289  ProcessExecutor executor(mFiles, mFileSettings, settings, suppressions, stdLogger, CppCheckExecutor::executeCommand);
290  returnValue = executor.check();
291  }
292 #endif
293  }
294 
295  returnValue |= cppcheck.analyseWholeProgram(settings.buildDir, mFiles, mFileSettings);
296 
297  if (settings.severity.isEnabled(Severity::information) || settings.checkConfiguration) {
298  const bool err = reportSuppressions(settings, suppressions, settings.checks.isEnabled(Checks::unusedFunction), mFiles, mFileSettings, stdLogger);
299  if (err && returnValue == 0)
300  returnValue = settings.exitCode;
301  }
302 
303  if (!settings.checkConfiguration) {
304  cppcheck.tooManyConfigsError(emptyString,0U);
305  }
306 
307  if (settings.safety || settings.severity.isEnabled(Severity::information) || !settings.checkersReportFilename.empty())
308  stdLogger.writeCheckersReport();
309 
310  if (settings.xml) {
311  stdLogger.reportErr(ErrorMessage::getXMLFooter());
312  }
313 
314  if (settings.safety && (stdLogger.hasCriticalErrors() || returnValue != 0))
315  return EXIT_FAILURE;
316 
317  if (returnValue)
318  return settings.exitCode;
319  return EXIT_SUCCESS;
320 }
321 
322 void StdLogger::writeCheckersReport()
323 {
324  CheckersReport checkersReport(mSettings, mActiveCheckers);
325 
326  bool suppressed = false;
327  for (const SuppressionList::Suppression& s : mSettings.supprs.nomsg.getSuppressions()) {
328  if (s.errorId == "checkersReport")
329  suppressed = true;
330  }
331 
332  if (!suppressed) {
333  ErrorMessage msg;
335  msg.id = "checkersReport";
336 
337  const int activeCheckers = checkersReport.getActiveCheckersCount();
338  const int totalCheckers = checkersReport.getAllCheckersCount();
339 
340  std::string what;
341  if (mCriticalErrors.empty())
342  what = std::to_string(activeCheckers) + "/" + std::to_string(totalCheckers);
343  else
344  what = "There was critical errors";
345  msg.setmsg("Active checkers: " + what + " (use --checkers-report=<filename> to see details)");
346 
347  reportErr(msg);
348  }
349 
350  if (!mSettings.checkersReportFilename.empty()) {
351  std::ofstream fout(mSettings.checkersReportFilename);
352  if (fout.is_open())
353  fout << checkersReport.getReport(mCriticalErrors);
354  }
355 }
356 
357 #ifdef _WIN32
358 // fix trac ticket #439 'Cppcheck reports wrong filename for filenames containing 8-bit ASCII'
359 static inline std::string ansiToOEM(const std::string &msg, bool doConvert)
360 {
361  if (doConvert) {
362  const unsigned msglength = msg.length();
363  // convert ANSI strings to OEM strings in two steps
364  std::vector<WCHAR> wcContainer(msglength);
365  std::string result(msglength, '\0');
366 
367  // ansi code page characters to wide characters
368  MultiByteToWideChar(CP_ACP, 0, msg.data(), msglength, wcContainer.data(), msglength);
369  // wide characters to oem codepage characters
370  WideCharToMultiByte(CP_OEMCP, 0, wcContainer.data(), msglength, &result[0], msglength, nullptr, nullptr);
371 
372  return result; // hope for return value optimization
373  }
374  return msg;
375 }
376 #else
377 // no performance regression on non-windows systems
378 #define ansiToOEM(msg, doConvert) (msg)
379 #endif
380 
381 void StdLogger::reportErr(const std::string &errmsg)
382 {
383  if (mErrorOutput)
384  *mErrorOutput << errmsg << std::endl;
385  else {
386  std::cerr << ansiToOEM(errmsg, !mSettings.xml) << std::endl;
387  }
388 }
389 
390 void StdLogger::reportOut(const std::string &outmsg, Color c)
391 {
392  if (c == Color::Reset)
393  std::cout << ansiToOEM(outmsg, true) << std::endl;
394  else
395  std::cout << c << ansiToOEM(outmsg, true) << Color::Reset << std::endl;
396 }
397 
398 // TODO: remove filename parameter?
399 void StdLogger::reportProgress(const std::string &filename, const char stage[], const std::size_t value)
400 {
401  (void)filename;
402 
403  if (!mLatestProgressOutputTime)
404  return;
405 
406  // Report progress messages every x seconds
407  const std::time_t currentTime = std::time(nullptr);
408  if (currentTime >= (mLatestProgressOutputTime + mSettings.reportProgress))
409  {
410  mLatestProgressOutputTime = currentTime;
411 
412  // format a progress message
413  std::ostringstream ostr;
414  ostr << "progress: "
415  << stage
416  << ' ' << value << '%';
417 
418  // Report progress message
419  reportOut(ostr.str());
420  }
421 }
422 
423 void StdLogger::reportErr(const ErrorMessage &msg)
424 {
425  if (msg.severity == Severity::internal && (msg.id == "logChecker" || endsWith(msg.id, "-logChecker"))) {
426  const std::string& checker = msg.shortMessage();
427  mActiveCheckers.emplace(checker);
428  return;
429  }
430 
431  if (ErrorLogger::isCriticalErrorId(msg.id) && mCriticalErrors.find(msg.id) == std::string::npos) {
432  if (!mCriticalErrors.empty())
433  mCriticalErrors += ", ";
434  mCriticalErrors += msg.id;
435  if (msg.severity == Severity::internal)
436  mCriticalErrors += " (suppressed)";
437  }
438 
439  if (msg.severity == Severity::internal)
440  return;
441 
442  // TODO: we generate a different message here then we log below
443  // TODO: there should be no need for verbose and default messages here
444  // Alert only about unique errors
445  if (!mShownErrors.insert(msg.toString(mSettings.verbose)).second)
446  return;
447 
448  if (mSettings.xml)
449  reportErr(msg.toXML());
450  else
451  reportErr(msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation));
452 }
453 
454 #if defined(USE_WINDOWS_SEH) || defined(USE_UNIX_SIGNAL_HANDLING)
455 void CppCheckExecutor::setExceptionOutput(FILE* exceptionOutput)
456 {
457  mExceptionOutput = exceptionOutput;
458 #if defined(USE_UNIX_SIGNAL_HANDLING)
459  set_signal_handler_output(mExceptionOutput);
460 #endif
461 }
462 
463 // cppcheck-suppress unusedFunction - only used by USE_WINDOWS_SEH code
465 {
466  return mExceptionOutput;
467 }
468 #endif
469 
470 /**
471  * Execute a shell command and read the output from it. Returns true if command terminated successfully.
472  */
473 // cppcheck-suppress passedByValueCallback - used as callback so we need to preserve the signature
474 // NOLINTNEXTLINE(performance-unnecessary-value-param) - used as callback so we need to preserve the signature
475 int CppCheckExecutor::executeCommand(std::string exe, std::vector<std::string> args, std::string redirect, std::string &output_)
476 {
477  output_.clear();
478 
479 #ifdef _WIN32
480  // Extra quoutes are needed in windows if filename has space
481  if (exe.find(" ") != std::string::npos)
482  exe = "\"" + exe + "\"";
483 #endif
484 
485  std::string joinedArgs;
486  for (const std::string &arg : args) {
487  if (!joinedArgs.empty())
488  joinedArgs += " ";
489  if (arg.find(' ') != std::string::npos)
490  joinedArgs += '"' + arg + '"';
491  else
492  joinedArgs += arg;
493  }
494 
495  const std::string cmd = exe + " " + joinedArgs + " " + redirect;
496 
497 #ifdef _WIN32
498  FILE* p = _popen(cmd.c_str(), "r");
499 #else
500  FILE *p = popen(cmd.c_str(), "r");
501 #endif
502  //std::cout << "invoking command '" << cmd << "'" << std::endl;
503  if (!p) {
504  // TODO: how to provide to caller?
505  //const int err = errno;
506  //std::cout << "popen() errno " << std::to_string(err) << std::endl;
507  return -1;
508  }
509  char buffer[1024];
510  while (fgets(buffer, sizeof(buffer), p) != nullptr)
511  output_ += buffer;
512 
513 #ifdef _WIN32
514  const int res = _pclose(p);
515 #else
516  const int res = pclose(p);
517 #endif
518  if (res == -1) { // error occurred
519  // TODO: how to provide to caller?
520  //const int err = errno;
521  //std::cout << "pclose() errno " << std::to_string(err) << std::endl;
522  return res;
523  }
524 #if !defined(WIN32) && !defined(__MINGW32__)
525  if (WIFEXITED(res)) {
526  return WEXITSTATUS(res);
527  }
528  if (WIFSIGNALED(res)) {
529  return WTERMSIG(res);
530  }
531 #endif
532  return res;
533 }
534 
static void writeFilesTxt(const std::string &buildDir, const std::list< std::string > &sourcefiles, const std::string &userDefines, const std::list< FileSettings > &fileSettings)
virtual void printMessage(const std::string &message)=0
print a regular message
virtual void printError(const std::string &message)=0
print an error message
virtual void printRaw(const std::string &message)=0
print to the output
The command line parser.
Definition: cmdlineparser.h:48
const std::list< FileWithDetails > & getFiles() const
Return the files user gave to command line.
Definition: cmdlineparser.h:87
bool fillSettingsFromArgs(int argc, const char *const argv[])
Parse command line args and fill settings and file lists from there.
const std::list< FileSettings > & getFileSettings() const
Return the file settings read from command line.
Definition: cmdlineparser.h:94
static void setExceptionOutput(FILE *exceptionOutput)
static FILE * getExceptionOutput()
static bool reportSuppressions(const Settings &settings, const SuppressionList &suppressions, bool unusedFunctionCheckEnabled, const std::list< FileWithDetails > &files, const std::list< FileSettings > &fileSettings, ErrorLogger &errorLogger)
std::list< FileSettings > mFileSettings
int check_wrapper(const Settings &settings)
Wrapper around check_internal.
int check(int argc, const char *const argv[])
Starts the checking.
static int executeCommand(std::string exe, std::vector< std::string > args, std::string redirect, std::string &output_)
Execute a shell command and read the output from it.
std::list< FileWithDetails > mFiles
Filename associated with size of file.
int check_internal(const Settings &settings) const
Starts the checking.
This is the base class which will use other classes to do static code analysis for C and C++ code to ...
Definition: cppcheck.h:60
This is an interface, which the class responsible of error logging should implement.
Definition: errorlogger.h:214
static bool isCriticalErrorId(const std::string &id)
Definition: errorlogger.h:264
virtual void reportErr(const ErrorMessage &msg)=0
Information about found errors and warnings is directed here.
virtual void reportOut(const std::string &outmsg, Color c=Color::Reset)=0
Information about progress is directed here.
virtual void reportProgress(const std::string &filename, const char stage[], const std::size_t value)
Report progress to client.
Definition: errorlogger.h:241
Wrapper for error messages, provided by reportErr()
Definition: errorlogger.h:48
static std::string getXMLFooter()
static std::string getXMLHeader(std::string productName)
Severity severity
Definition: errorlogger.h:170
std::string toString(bool verbose, const std::string &templateFormat=emptyString, const std::string &templateLocation=emptyString) const
Format the error message into a string.
const std::string & shortMessage() const
Short message (single line short message)
Definition: errorlogger.h:181
std::string id
Definition: errorlogger.h:165
std::string toXML() const
Format the error message in XML format.
void setmsg(const std::string &msg)
set short and verbose messages
This class will take a list of filenames and settings and check then all files using threads.
unsigned int check() override
This is just a container for general settings so that we don't need to pass individual values to func...
Definition: settings.h:95
void loadSummaries()
Definition: settings.cpp:283
int exitCode
If errors are found, this value is returned from main().
Definition: settings.h:214
int reportProgress
–report-progress
Definition: settings.h:288
Suppressions supprs
suppressions
Definition: settings.h:369
SimpleEnableGroup< Checks > checks
Definition: settings.h:360
bool xml
write XML results (–xml)
Definition: settings.h:401
bool checkConfiguration
Is the 'configuration checking' wanted?
Definition: settings.h:127
static bool terminated()
termination requested?
Definition: settings.h:449
void setMisraRuleTexts(const ExecuteCmdFn &executeCommand)
Definition: settings.cpp:572
bool safety
Safety certified behavior Show checkers report when Cppcheck finishes Make cppcheck checking more str...
Definition: settings.h:313
std::string buildDir
–cppcheck-build-dir.
Definition: settings.h:121
std::string cppcheckCfgProductName
cppcheck.cfg: Custom product name
Definition: settings.h:165
std::string userDefines
defines given by the user
Definition: settings.h:386
std::string outputFile
write results (–output-file=<file>)
Definition: settings.h:253
bool useSingleJob() const
Definition: settings.h:457
ExecutorType executor
Definition: settings.h:207
SimpleEnableGroup< Severity > severity
Definition: settings.h:358
std::string checkersReportFilename
–checkers-report=<filename> : Generate report of executed checkers
Definition: settings.h:141
bool isEnabled(T flag) const
Definition: settings.h:66
unsigned int check() override
class for handling suppressions
Definition: suppressions.h:42
std::list< Suppression > getUnmatchedGlobalSuppressions(const bool unusedFunctionChecking) const
Returns list of unmatched global (glob pattern) suppressions.
const std::list< Suppression > & getSuppressions() const
Returns list of all suppressions.
static bool reportUnmatchedSuppressions(const std::list< SuppressionList::Suppression > &unmatched, ErrorLogger &errorLogger)
Report unmatched suppressions.
std::list< Suppression > getUnmatchedLocalSuppressions(const std::string &file, const bool unusedFunctionChecking) const
Returns list of unmatched local (per-file) suppressions.
This class will take a list of filenames and settings and check then all files using threads.
unsigned int check() override
Color
Definition: color.h:27
static const std::string emptyString
Definition: config.h:127
#define ansiToOEM(msg, doConvert)
@ information
Checking information.
@ internal
Internal message.
@ unusedFunction
bool endsWith(const std::string &str, char c)
Definition: utils.h:110