Cppcheck
processexecutor.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 "processexecutor.h"
20 
21 #if !defined(WIN32) && !defined(__MINGW32__)
22 
23 #include "config.h"
24 #include "cppcheck.h"
25 #include "errorlogger.h"
26 #include "errortypes.h"
27 #include "filesettings.h"
28 #include "settings.h"
29 #include "suppressions.h"
30 #include "timer.h"
31 
32 #include <algorithm>
33 #include <numeric>
34 #include <cassert>
35 #include <cerrno>
36 #include <csignal>
37 #include <cstdlib>
38 #include <cstring>
39 #include <iostream>
40 #include <list>
41 #include <map>
42 #include <sstream>
43 #include <sys/select.h>
44 #include <sys/time.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #include <unistd.h>
48 #include <utility>
49 #include <fcntl.h>
50 
51 
52 #ifdef __SVR4 // Solaris
53 #include <sys/loadavg.h>
54 #endif
55 
56 #if defined(__linux__)
57 #include <sys/prctl.h>
58 #endif
59 
60 enum class Color;
61 
62 // NOLINTNEXTLINE(misc-unused-using-decls) - required for FD_ZERO
63 using std::memset;
64 
65 
66 ProcessExecutor::ProcessExecutor(const std::list<FileWithDetails> &files, const std::list<FileSettings>& fileSettings, const Settings &settings, SuppressionList &suppressions, ErrorLogger &errorLogger, CppCheck::ExecuteCmdFn executeCommand)
67  : Executor(files, fileSettings, settings, suppressions, errorLogger)
68  , mExecuteCommand(std::move(executeCommand))
69 {
70  assert(mSettings.jobs > 1);
71 }
72 
73 namespace {
74  class PipeWriter : public ErrorLogger {
75  public:
76  enum PipeSignal {REPORT_OUT='1',REPORT_ERROR='2', CHILD_END='5'};
77 
78  explicit PipeWriter(int pipe) : mWpipe(pipe) {}
79 
80  void reportOut(const std::string &outmsg, Color c) override {
81  writeToPipe(REPORT_OUT, static_cast<char>(c) + outmsg);
82  }
83 
84  void reportErr(const ErrorMessage &msg) override {
85  writeToPipe(REPORT_ERROR, msg.serialize());
86  }
87 
88  void writeEnd(const std::string& str) const {
89  writeToPipe(CHILD_END, str);
90  }
91 
92  private:
93  // TODO: how to log file name in error?
94  void writeToPipeInternal(PipeSignal type, const void* data, std::size_t to_write) const
95  {
96  const ssize_t bytes_written = write(mWpipe, data, to_write);
97  if (bytes_written <= 0) {
98  const int err = errno;
99  std::cerr << "#### ThreadExecutor::writeToPipeInternal() error for type " << type << ": " << std::strerror(err) << std::endl;
100  std::exit(EXIT_FAILURE);
101  }
102  // TODO: write until everything is written
103  if (bytes_written != to_write) {
104  std::cerr << "#### ThreadExecutor::writeToPipeInternal() error for type " << type << ": insufficient data written (expected: " << to_write << " / got: " << bytes_written << ")" << std::endl;
105  std::exit(EXIT_FAILURE);
106  }
107  }
108 
109  void writeToPipe(PipeSignal type, const std::string &data) const
110  {
111  {
112  const auto t = static_cast<char>(type);
113  writeToPipeInternal(type, &t, 1);
114  }
115 
116  const auto len = static_cast<unsigned int>(data.length());
117  {
118  static constexpr std::size_t l_size = sizeof(unsigned int);
119  writeToPipeInternal(type, &len, l_size);
120  }
121 
122  writeToPipeInternal(type, data.c_str(), len);
123  }
124 
125  const int mWpipe;
126  };
127 }
128 
129 bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::string& filename)
130 {
131  std::size_t bytes_to_read;
132  ssize_t bytes_read;
133 
134  char type = 0;
135  bytes_to_read = sizeof(char);
136  bytes_read = read(rpipe, &type, bytes_to_read);
137  if (bytes_read <= 0) {
138  if (errno == EAGAIN)
139  return true;
140 
141  // TODO: log details about failure
142 
143  // need to increment so a missing pipe (i.e. premature exit of forked process) results in an error exitcode
144  ++result;
145  return false;
146  }
147  if (bytes_read != bytes_to_read) {
148  std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") error (type): insufficient data read (expected: " << bytes_to_read << " / got: " << bytes_read << ")" << std::endl;
149  std::exit(EXIT_FAILURE);
150  }
151 
152  if (type != PipeWriter::REPORT_OUT && type != PipeWriter::REPORT_ERROR && type != PipeWriter::CHILD_END) {
153  std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") invalid type " << int(type) << std::endl;
154  std::exit(EXIT_FAILURE);
155  }
156 
157  unsigned int len = 0;
158  bytes_to_read = sizeof(len);
159  bytes_read = read(rpipe, &len, bytes_to_read);
160  if (bytes_read <= 0) {
161  const int err = errno;
162  std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") error (len) for type " << int(type) << ": " << std::strerror(err) << std::endl;
163  std::exit(EXIT_FAILURE);
164  }
165  if (bytes_read != bytes_to_read) {
166  std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") error (len) for type" << int(type) << ": insufficient data read (expected: " << bytes_to_read << " / got: " << bytes_read << ")" << std::endl;
167  std::exit(EXIT_FAILURE);
168  }
169 
170  std::string buf(len, '\0');
171  char *data_start = &buf[0];
172  bytes_to_read = len;
173  do {
174  bytes_read = read(rpipe, data_start, bytes_to_read);
175  if (bytes_read <= 0) {
176  const int err = errno;
177  std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") error (buf) for type" << int(type) << ": " << std::strerror(err) << std::endl;
178  std::exit(EXIT_FAILURE);
179  }
180  bytes_to_read -= bytes_read;
181  data_start += bytes_read;
182  } while (bytes_to_read != 0);
183 
184  bool res = true;
185  if (type == PipeWriter::REPORT_OUT) {
186  // the first character is the color
187  const Color c = static_cast<Color>(buf[0]);
188  // TODO: avoid string copy
189  mErrorLogger.reportOut(buf.substr(1), c);
190  } else if (type == PipeWriter::REPORT_ERROR) {
191  ErrorMessage msg;
192  try {
193  msg.deserialize(buf);
194  } catch (const InternalError& e) {
195  std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") internal error: " << e.errorMessage << std::endl;
196  std::exit(EXIT_FAILURE);
197  }
198 
199  if (hasToLog(msg))
200  mErrorLogger.reportErr(msg);
201  } else if (type == PipeWriter::CHILD_END) {
202  result += std::stoi(buf);
203  res = false;
204  }
205 
206  return res;
207 }
208 
209 bool ProcessExecutor::checkLoadAverage(size_t nchildren)
210 {
211 #if defined(__QNX__) || defined(__HAIKU__) // getloadavg() is unsupported on Qnx, Haiku.
212  (void)nchildren;
213  return true;
214 #else
215  if (!nchildren || !mSettings.loadAverage) {
216  return true;
217  }
218 
219  double sample(0);
220  if (getloadavg(&sample, 1) != 1) {
221  // disable load average checking on getloadavg error
222  return true;
223  }
224  if (sample < mSettings.loadAverage) {
225  return true;
226  }
227  return false;
228 #endif
229 }
230 
232 {
233  unsigned int fileCount = 0;
234  unsigned int result = 0;
235 
236  const std::size_t totalfilesize = std::accumulate(mFiles.cbegin(), mFiles.cend(), std::size_t(0), [](std::size_t v, const FileWithDetails& p) {
237  return v + p.size();
238  });
239 
240  std::list<int> rpipes;
241  std::map<pid_t, std::string> childFile;
242  std::map<int, std::string> pipeFile;
243  std::size_t processedsize = 0;
244  std::list<FileWithDetails>::const_iterator iFile = mFiles.cbegin();
245  std::list<FileSettings>::const_iterator iFileSettings = mFileSettings.cbegin();
246  for (;;) {
247  // Start a new child
248  const size_t nchildren = childFile.size();
249  if ((iFile != mFiles.cend() || iFileSettings != mFileSettings.cend()) && nchildren < mSettings.jobs && checkLoadAverage(nchildren)) {
250  int pipes[2];
251  if (pipe(pipes) == -1) {
252  std::cerr << "#### ThreadExecutor::check, pipe() failed: "<< std::strerror(errno) << std::endl;
253  std::exit(EXIT_FAILURE);
254  }
255 
256  const int flags = fcntl(pipes[0], F_GETFL, 0);
257  if (flags < 0) {
258  std::cerr << "#### ThreadExecutor::check, fcntl(F_GETFL) failed: "<< std::strerror(errno) << std::endl;
259  std::exit(EXIT_FAILURE);
260  }
261 
262  if (fcntl(pipes[0], F_SETFL, flags) < 0) {
263  std::cerr << "#### ThreadExecutor::check, fcntl(F_SETFL) failed: "<< std::strerror(errno) << std::endl;
264  std::exit(EXIT_FAILURE);
265  }
266 
267  const pid_t pid = fork();
268  if (pid < 0) {
269  // Error
270  std::cerr << "#### ThreadExecutor::check, Failed to create child process: "<< std::strerror(errno) << std::endl;
271  std::exit(EXIT_FAILURE);
272  } else if (pid == 0) {
273 #if defined(__linux__)
274  prctl(PR_SET_PDEATHSIG, SIGHUP);
275 #endif
276  close(pipes[0]);
277 
278  PipeWriter pipewriter(pipes[1]);
279  CppCheck fileChecker(pipewriter, false, mExecuteCommand);
280  fileChecker.settings() = mSettings;
281  unsigned int resultOfCheck = 0;
282 
283  if (iFileSettings != mFileSettings.end()) {
284  resultOfCheck = fileChecker.check(*iFileSettings);
285  if (fileChecker.settings().clangTidy)
286  fileChecker.analyseClangTidy(*iFileSettings);
287  } else {
288  // Read file from a file
289  resultOfCheck = fileChecker.check(iFile->path());
290  // TODO: call analyseClangTidy()?
291  }
292 
293  pipewriter.writeEnd(std::to_string(resultOfCheck));
294  std::exit(EXIT_SUCCESS);
295  }
296 
297  close(pipes[1]);
298  rpipes.push_back(pipes[0]);
299  if (iFileSettings != mFileSettings.end()) {
300  childFile[pid] = iFileSettings->filename() + ' ' + iFileSettings->cfg;
301  pipeFile[pipes[0]] = iFileSettings->filename() + ' ' + iFileSettings->cfg;
302  ++iFileSettings;
303  } else {
304  childFile[pid] = iFile->path();
305  pipeFile[pipes[0]] = iFile->path();
306  ++iFile;
307  }
308  }
309  if (!rpipes.empty()) {
310  fd_set rfds;
311  FD_ZERO(&rfds);
312  for (std::list<int>::const_iterator rp = rpipes.cbegin(); rp != rpipes.cend(); ++rp)
313  FD_SET(*rp, &rfds);
314  timeval tv; // for every second polling of load average condition
315  tv.tv_sec = 1;
316  tv.tv_usec = 0;
317  const int r = select(*std::max_element(rpipes.cbegin(), rpipes.cend()) + 1, &rfds, nullptr, nullptr, &tv);
318 
319  if (r > 0) {
320  std::list<int>::iterator rp = rpipes.begin();
321  while (rp != rpipes.end()) {
322  if (FD_ISSET(*rp, &rfds)) {
323  std::string name;
324  const std::map<int, std::string>::iterator p = pipeFile.find(*rp);
325  if (p != pipeFile.end()) {
326  name = p->second;
327  }
328  const bool readRes = handleRead(*rp, result, name);
329  if (!readRes) {
330  std::size_t size = 0;
331  if (p != pipeFile.end()) {
332  pipeFile.erase(p);
333  const auto fs = std::find_if(mFiles.cbegin(), mFiles.cend(), [&name](const FileWithDetails& entry) {
334  return entry.path() == name;
335  });
336  if (fs != mFiles.end()) {
337  size = fs->size();
338  }
339  }
340 
341  fileCount++;
342  processedsize += size;
343  if (!mSettings.quiet)
344  Executor::reportStatus(fileCount, mFiles.size() + mFileSettings.size(), processedsize, totalfilesize);
345 
346  close(*rp);
347  rp = rpipes.erase(rp);
348  } else
349  ++rp;
350  } else
351  ++rp;
352  }
353  }
354  }
355  if (!childFile.empty()) {
356  int stat = 0;
357  const pid_t child = waitpid(0, &stat, WNOHANG);
358  if (child > 0) {
359  std::string childname;
360  const std::map<pid_t, std::string>::iterator c = childFile.find(child);
361  if (c != childFile.end()) {
362  childname = c->second;
363  childFile.erase(c);
364  }
365 
366  if (WIFEXITED(stat)) {
367  const int exitstatus = WEXITSTATUS(stat);
368  if (exitstatus != EXIT_SUCCESS) {
369  std::ostringstream oss;
370  oss << "Child process exited with " << exitstatus;
371  reportInternalChildErr(childname, oss.str());
372  }
373  } else if (WIFSIGNALED(stat)) {
374  std::ostringstream oss;
375  oss << "Child process crashed with signal " << WTERMSIG(stat);
376  reportInternalChildErr(childname, oss.str());
377  }
378  }
379  }
380  if (iFile == mFiles.end() && iFileSettings == mFileSettings.end() && rpipes.empty() && childFile.empty()) {
381  // All done
382  break;
383  }
384  }
385 
386  // TODO: wee need to get the timing information from the subprocess
389 
390  return result;
391 }
392 
393 void ProcessExecutor::reportInternalChildErr(const std::string &childname, const std::string &msg)
394 {
395  std::list<ErrorMessage::FileLocation> locations;
396  locations.emplace_back(childname, 0, 0);
397  const ErrorMessage errmsg(std::move(locations),
398  emptyString,
400  "Internal error: " + msg,
401  "cppcheckError",
403 
404  if (!mSuppressions.isSuppressed(errmsg, {}))
405  mErrorLogger.reportErr(errmsg);
406 }
407 
408 #endif // !WIN32
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
static void printTimerResults(SHOWTIME_MODES mode)
Definition: cppcheck.cpp:1850
Settings & settings()
Get reference to current settings.
Definition: cppcheck.cpp:1487
std::function< int(std::string, std::vector< std::string >, std::string, std::string &)> ExecuteCmdFn
Definition: cppcheck.h:62
unsigned int check(const std::string &path)
This starts the actual checking.
Definition: cppcheck.cpp:542
void analyseClangTidy(const FileSettings &fileSettings)
Analyze all files using clang-tidy.
Definition: cppcheck.cpp:1656
This is an interface, which the class responsible of error logging should implement.
Definition: errorlogger.h:214
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.
Wrapper for error messages, provided by reportErr()
Definition: errorlogger.h:48
std::string serialize() const
void deserialize(const std::string &data)
This class will take a list of filenames and settings and check then all files using threads.
Definition: executor.h:43
bool hasToLog(const ErrorMessage &msg)
Check if message is being suppressed and unique.
Definition: executor.cpp:41
const std::list< FileSettings > & mFileSettings
Definition: executor.h:72
SuppressionList & mSuppressions
Definition: executor.h:74
const std::list< FileWithDetails > & mFiles
Definition: executor.h:71
void reportStatus(std::size_t fileindex, std::size_t filecount, std::size_t sizedone, std::size_t sizetotal)
Information about how many files have been checked.
Definition: executor.cpp:61
ErrorLogger & mErrorLogger
Definition: executor.h:75
const Settings & mSettings
Definition: executor.h:73
void reportInternalChildErr(const std::string &childname, const std::string &msg)
Reports internal errors related to child processes.
CppCheck::ExecuteCmdFn mExecuteCommand
bool checkLoadAverage(size_t nchildren)
Check load average condition.
bool handleRead(int rpipe, unsigned int &result, const std::string &filename)
Read from the pipe, parse and handle what ever is in there.
unsigned int check() override
ProcessExecutor(const std::list< FileWithDetails > &files, const std::list< FileSettings > &fileSettings, const Settings &settings, SuppressionList &suppressions, ErrorLogger &errorLogger, CppCheck::ExecuteCmdFn executeCommand)
This is just a container for general settings so that we don't need to pass individual values to func...
Definition: settings.h:95
bool quiet
Is –quiet given?
Definition: settings.h:282
int loadAverage
Load average value.
Definition: settings.h:240
bool clangTidy
Use clang-tidy.
Definition: settings.h:156
unsigned int jobs
How many processes/threads should do checking at the same time.
Definition: settings.h:231
SHOWTIME_MODES showtime
show timing information (–showtime=file|summary|top5)
Definition: settings.h:363
class for handling suppressions
Definition: suppressions.h:42
bool isSuppressed(const ErrorMessage &errmsg, bool global=true)
Returns true if this message should not be shown to the user.
Color
Definition: color.h:27
static const std::string emptyString
Definition: config.h:127
@ error
Programming error.
Simple container to be thrown when internal error is detected.
Definition: errortypes.h:36
std::string errorMessage
Definition: errortypes.h:43