From b51aea5531a1eeb49f6dad160142312186e375bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 8 Jul 2022 16:42:57 +0200 Subject: [PATCH] separated process execution code into `ProcessExecutor` (#4249) --- Makefile | 24 ++- cli/CMakeLists.txt | 4 +- cli/cli.vcxproj | 4 + cli/cppcheckexecutor.cpp | 11 +- cli/executor.cpp | 30 +++ cli/executor.h | 60 ++++++ cli/processexecutor.cpp | 383 +++++++++++++++++++++++++++++++++++ cli/processexecutor.h | 72 +++++++ cli/threadexecutor.cpp | 375 ++-------------------------------- cli/threadexecutor.h | 48 +---- cmake/findDependencies.cmake | 4 +- cmake/printInfo.cmake | 4 +- lib/checkother.cpp | 1 + lib/checkunusedvar.cpp | 1 + lib/config.h | 1 + lib/ctu.cpp | 1 + lib/ctu.h | 1 - lib/path.cpp | 1 - lib/pathmatch.cpp | 2 - test/CMakeLists.txt | 4 +- test/testprocessexecutor.cpp | 142 +++++++++++++ test/testrunner.vcxproj | 3 + tools/dmake.cpp | 4 +- 23 files changed, 752 insertions(+), 428 deletions(-) create mode 100644 cli/executor.cpp create mode 100644 cli/executor.h create mode 100644 cli/processexecutor.cpp create mode 100644 cli/processexecutor.h create mode 100644 test/testprocessexecutor.cpp diff --git a/Makefile b/Makefile index 22cb89c9e..1d8603454 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,8 @@ else # !WINNT endif # !CPPCHK_GLIBCXX_DEBUG endif # GNU/kFreeBSD + LDFLAGS=-pthread + endif # WINNT ifdef CYGWIN @@ -207,8 +209,10 @@ EXTOBJ = externals/simplecpp/simplecpp.o \ CLIOBJ = cli/cmdlineparser.o \ cli/cppcheckexecutor.o \ + cli/executor.o \ cli/filelister.o \ cli/main.o \ + cli/processexecutor.o \ cli/threadexecutor.o TESTOBJ = test/options.o \ @@ -247,6 +251,7 @@ TESTOBJ = test/options.o \ test/testplatform.o \ test/testpostfixoperator.o \ test/testpreprocessor.o \ + test/testprocessexecutor.o \ test/testrunner.o \ test/testsimplifytemplate.o \ test/testsimplifytokens.o \ @@ -285,7 +290,7 @@ cppcheck: $(LIBOBJ) $(CLIOBJ) $(EXTOBJ) all: cppcheck testrunner -testrunner: $(TESTOBJ) $(LIBOBJ) $(EXTOBJ) cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/filelister.o +testrunner: $(TESTOBJ) $(LIBOBJ) $(EXTOBJ) cli/executor.o cli/processexecutor.o cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/filelister.o $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LIBS) $(LDFLAGS) $(RDYNAMIC) test: all @@ -567,16 +572,22 @@ $(libcppdir)/valueflow.o: lib/valueflow.cpp lib/analyzer.h lib/astutils.h lib/ca cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlineparser.h cli/cppcheckexecutor.h cli/filelister.h externals/tinyxml2/tinyxml2.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/cmdlineparser.o cli/cmdlineparser.cpp -cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlineparser.h cli/cppcheckexecutor.h cli/filelister.h cli/threadexecutor.h externals/simplecpp/simplecpp.h lib/analyzerinfo.h lib/check.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h +cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlineparser.h cli/cppcheckexecutor.h cli/executor.h cli/filelister.h cli/processexecutor.h cli/threadexecutor.h externals/simplecpp/simplecpp.h lib/analyzerinfo.h lib/check.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/cppcheckexecutor.o cli/cppcheckexecutor.cpp +cli/executor.o: cli/executor.cpp cli/executor.h + $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/executor.o cli/executor.cpp + cli/filelister.o: cli/filelister.cpp cli/filelister.h lib/config.h lib/path.h lib/pathmatch.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/filelister.o cli/filelister.cpp cli/main.o: cli/main.cpp cli/cppcheckexecutor.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/suppressions.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/main.o cli/main.cpp -cli/threadexecutor.o: cli/threadexecutor.cpp cli/cppcheckexecutor.h cli/threadexecutor.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h +cli/processexecutor.o: cli/processexecutor.cpp cli/cppcheckexecutor.h cli/executor.h cli/processexecutor.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h + $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/processexecutor.o cli/processexecutor.cpp + +cli/threadexecutor.o: cli/threadexecutor.cpp cli/cppcheckexecutor.h cli/executor.h cli/threadexecutor.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o cli/threadexecutor.o cli/threadexecutor.cpp test/options.o: test/options.cpp test/options.h @@ -687,6 +698,9 @@ test/testpostfixoperator.o: test/testpostfixoperator.cpp lib/check.h lib/checkpo test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testpreprocessor.o test/testpreprocessor.cpp +test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h test/testutils.h + $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testprocessexecutor.o test/testprocessexecutor.cpp + test/testrunner.o: test/testrunner.cpp externals/simplecpp/simplecpp.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/preprocessor.h lib/suppressions.h test/options.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testrunner.o test/testrunner.cpp @@ -717,13 +731,13 @@ test/testsuite.o: test/testsuite.cpp lib/color.h lib/config.h lib/errorlogger.h test/testsummaries.o: test/testsummaries.cpp lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/summaries.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testsummaries.o test/testsummaries.cpp -test/testsuppressions.o: test/testsuppressions.cpp cli/threadexecutor.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h test/testutils.h +test/testsuppressions.o: test/testsuppressions.cpp cli/executor.h cli/threadexecutor.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h test/testutils.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testsuppressions.o test/testsuppressions.cpp test/testsymboldatabase.o: test/testsymboldatabase.cpp externals/tinyxml2/tinyxml2.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h test/testutils.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testsymboldatabase.o test/testsymboldatabase.cpp -test/testthreadexecutor.o: test/testthreadexecutor.cpp cli/threadexecutor.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h test/testutils.h +test/testthreadexecutor.o: test/testthreadexecutor.cpp cli/executor.h cli/threadexecutor.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h test/testsuite.h test/testutils.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testthreadexecutor.o test/testthreadexecutor.cpp test/testtimer.o: test/testtimer.cpp lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/suppressions.h lib/timer.h test/testsuite.h diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 20915ae4e..3b99ac1b2 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -39,9 +39,7 @@ endif() if(tinyxml2_FOUND AND NOT USE_BUNDLED_TINYXML2) target_link_libraries(cppcheck ${tinyxml2_LIBRARIES}) endif() -if (USE_THREADS) - target_link_libraries(cppcheck ${CMAKE_THREAD_LIBS_INIT}) -endif() +target_link_libraries(cppcheck ${CMAKE_THREAD_LIBS_INIT}) add_dependencies(cppcheck copy_cfg) add_dependencies(cppcheck copy_addons) diff --git a/cli/cli.vcxproj b/cli/cli.vcxproj index c242bad09..9a2db8671 100644 --- a/cli/cli.vcxproj +++ b/cli/cli.vcxproj @@ -407,7 +407,9 @@ + + @@ -418,8 +420,10 @@ + + diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 69aea810c..14d40bbb5 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -36,6 +36,12 @@ #include "utils.h" #include "checkunusedfunctions.h" +#if defined(THREADING_MODEL_THREAD) +#include "threadexecutor.h" +#elif defined(THREADING_MODEL_FORK) +#include "processexecutor.h" +#endif + #include #include #include @@ -972,8 +978,11 @@ int CppCheckExecutor::check_internal(CppCheck& cppcheck) } else if (!ThreadExecutor::isEnabled()) { std::cout << "No thread support yet implemented for this platform." << std::endl; } else { - // Multiple processes +#if defined(THREADING_MODEL_THREAD) ThreadExecutor executor(mFiles, settings, *this); +#elif defined(THREADING_MODEL_FORK) + ProcessExecutor executor(mFiles, settings, *this); +#endif returnValue = executor.check(); } diff --git a/cli/executor.cpp b/cli/executor.cpp new file mode 100644 index 000000000..762a39847 --- /dev/null +++ b/cli/executor.cpp @@ -0,0 +1,30 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "executor.h" + +Executor::Executor(const std::map &files, Settings &settings, ErrorLogger &errorLogger) + : mFiles(files), mSettings(settings), mErrorLogger(errorLogger) +{} + +Executor::~Executor() +{} + +bool Executor::isEnabled() { + return true; +} diff --git a/cli/executor.h b/cli/executor.h new file mode 100644 index 000000000..866097c0b --- /dev/null +++ b/cli/executor.h @@ -0,0 +1,60 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EXECUTOR_H +#define EXECUTOR_H + +#include +#include +#include +#include + +class Settings; +class ErrorLogger; + +/// @addtogroup CLI +/// @{ + +/** + * This class will take a list of filenames and settings and check then + * all files using threads. + */ +class Executor { +public: + Executor(const std::map &files, Settings &settings, ErrorLogger &errorLogger); + Executor(const Executor &) = delete; + virtual ~Executor(); + void operator=(const Executor &) = delete; + + virtual unsigned int check() = 0; + + /** + * @return true if support for threads exist. + */ + static bool isEnabled(); + +protected: + const std::map &mFiles; + Settings &mSettings; + ErrorLogger &mErrorLogger; + std::list mErrorList; +}; + +/// @} + +#endif // EXECUTOR_H diff --git a/cli/processexecutor.cpp b/cli/processexecutor.cpp new file mode 100644 index 000000000..a248e078d --- /dev/null +++ b/cli/processexecutor.cpp @@ -0,0 +1,383 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "processexecutor.h" + +#if !defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) + +#include "color.h" +#include "config.h" +#include "cppcheck.h" +#include "cppcheckexecutor.h" +#include "errorlogger.h" +#include "errortypes.h" +#include "importproject.h" +#include "settings.h" +#include "suppressions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __SVR4 // Solaris +#include +#endif + +#if defined(__linux__) +#include +#endif + +// NOLINTNEXTLINE(misc-unused-using-decls) - required for FD_ZERO +using std::memset; + + +ProcessExecutor::ProcessExecutor(const std::map &files, Settings &settings, ErrorLogger &errorLogger) + : Executor(files, settings, errorLogger) +{} + +ProcessExecutor::~ProcessExecutor() +{} + +class PipeWriter : public ErrorLogger { +public: + enum PipeSignal {REPORT_OUT='1',REPORT_ERROR='2', REPORT_INFO='3', REPORT_VERIFICATION='4', CHILD_END='5'}; + + explicit PipeWriter(int pipe) : mWpipe(pipe) {} + + void reportOut(const std::string &outmsg, Color c) override { + writeToPipe(REPORT_OUT, ::toString(c) + outmsg + ::toString(Color::Reset)); + } + + void reportErr(const ErrorMessage &msg) override { + report(msg, MessageType::REPORT_ERROR); + } + + void reportInfo(const ErrorMessage &msg) override { + report(msg, MessageType::REPORT_INFO); + } + + void writeEnd(const std::string& str) { + writeToPipe(CHILD_END, str); + } + +private: + enum class MessageType {REPORT_ERROR, REPORT_INFO}; + + void report(const ErrorMessage &msg, MessageType msgType) { + PipeSignal pipeSignal; + switch (msgType) { + case MessageType::REPORT_ERROR: + pipeSignal = REPORT_ERROR; + break; + case MessageType::REPORT_INFO: + pipeSignal = REPORT_INFO; + break; + } + + writeToPipe(pipeSignal, msg.serialize()); + } + + void writeToPipe(PipeSignal type, const std::string &data) + { + unsigned int len = static_cast(data.length() + 1); + char *out = new char[len + 1 + sizeof(len)]; + out[0] = static_cast(type); + std::memcpy(&(out[1]), &len, sizeof(len)); + std::memcpy(&(out[1+sizeof(len)]), data.c_str(), len); + if (write(mWpipe, out, len + 1 + sizeof(len)) <= 0) { + delete[] out; + out = nullptr; + std::cerr << "#### ThreadExecutor::writeToPipe, Failed to write to pipe" << std::endl; + std::exit(EXIT_FAILURE); + } + + delete[] out; + } + + const int mWpipe; +}; + +int ProcessExecutor::handleRead(int rpipe, unsigned int &result) +{ + char type = 0; + if (read(rpipe, &type, 1) <= 0) { + if (errno == EAGAIN) + return 0; + + // need to increment so a missing pipe (i.e. premature exit of forked process) results in an error exitcode + ++result; + return -1; + } + + if (type != PipeWriter::REPORT_OUT && type != PipeWriter::REPORT_ERROR && type != PipeWriter::REPORT_INFO && type != PipeWriter::CHILD_END) { + std::cerr << "#### ThreadExecutor::handleRead error, type was:" << type << std::endl; + std::exit(EXIT_FAILURE); + } + + unsigned int len = 0; + if (read(rpipe, &len, sizeof(len)) <= 0) { + std::cerr << "#### ThreadExecutor::handleRead error, type was:" << type << std::endl; + std::exit(EXIT_FAILURE); + } + + // Don't rely on incoming data being null-terminated. + // Allocate +1 element and null-terminate the buffer. + char *buf = new char[len + 1]; + const ssize_t readIntoBuf = read(rpipe, buf, len); + if (readIntoBuf <= 0) { + std::cerr << "#### ThreadExecutor::handleRead error, type was:" << type << std::endl; + std::exit(EXIT_FAILURE); + } + buf[readIntoBuf] = 0; + + if (type == PipeWriter::REPORT_OUT) { + mErrorLogger.reportOut(buf); + } else if (type == PipeWriter::REPORT_ERROR || type == PipeWriter::REPORT_INFO) { + ErrorMessage msg; + try { + msg.deserialize(buf); + } catch (const InternalError& e) { + std::cerr << "#### ThreadExecutor::handleRead error, internal error:" << e.errorMessage << std::endl; + std::exit(EXIT_FAILURE); + } + + if (!mSettings.nomsg.isSuppressed(msg.toSuppressionsErrorMessage())) { + // Alert only about unique errors + std::string errmsg = msg.toString(mSettings.verbose); + if (std::find(mErrorList.begin(), mErrorList.end(), errmsg) == mErrorList.end()) { + mErrorList.emplace_back(errmsg); + if (type == PipeWriter::REPORT_ERROR) + mErrorLogger.reportErr(msg); + else + mErrorLogger.reportInfo(msg); + } + } + } else if (type == PipeWriter::CHILD_END) { + std::istringstream iss(buf); + unsigned int fileResult = 0; + iss >> fileResult; + result += fileResult; + delete[] buf; + return -1; + } + + delete[] buf; + return 1; +} + +bool ProcessExecutor::checkLoadAverage(size_t nchildren) +{ +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__HAIKU__) // getloadavg() is unsupported on Cygwin, Qnx, Haiku. + (void)nchildren; + return true; +#else + if (!nchildren || !mSettings.loadAverage) { + return true; + } + + double sample(0); + if (getloadavg(&sample, 1) != 1) { + // disable load average checking on getloadavg error + return true; + } else if (sample < mSettings.loadAverage) { + return true; + } + return false; +#endif +} + +unsigned int ProcessExecutor::check() +{ + unsigned int fileCount = 0; + unsigned int result = 0; + + std::size_t totalfilesize = 0; + for (std::map::const_iterator i = mFiles.begin(); i != mFiles.end(); ++i) { + totalfilesize += i->second; + } + + std::list rpipes; + std::map childFile; + std::map pipeFile; + std::size_t processedsize = 0; + std::map::const_iterator iFile = mFiles.begin(); + std::list::const_iterator iFileSettings = mSettings.project.fileSettings.begin(); + for (;;) { + // Start a new child + size_t nchildren = childFile.size(); + if ((iFile != mFiles.end() || iFileSettings != mSettings.project.fileSettings.end()) && nchildren < mSettings.jobs && checkLoadAverage(nchildren)) { + int pipes[2]; + if (pipe(pipes) == -1) { + std::cerr << "#### ThreadExecutor::check, pipe() failed: "<< std::strerror(errno) << std::endl; + std::exit(EXIT_FAILURE); + } + + int flags = 0; + if ((flags = fcntl(pipes[0], F_GETFL, 0)) < 0) { + std::cerr << "#### ThreadExecutor::check, fcntl(F_GETFL) failed: "<< std::strerror(errno) << std::endl; + std::exit(EXIT_FAILURE); + } + + if (fcntl(pipes[0], F_SETFL, flags | O_NONBLOCK) < 0) { + std::cerr << "#### ThreadExecutor::check, fcntl(F_SETFL) failed: "<< std::strerror(errno) << std::endl; + std::exit(EXIT_FAILURE); + } + + pid_t pid = fork(); + if (pid < 0) { + // Error + std::cerr << "#### ThreadExecutor::check, Failed to create child process: "<< std::strerror(errno) << std::endl; + std::exit(EXIT_FAILURE); + } else if (pid == 0) { +#if defined(__linux__) + prctl(PR_SET_PDEATHSIG, SIGHUP); +#endif + close(pipes[0]); + + PipeWriter pipewriter(pipes[1]); + CppCheck fileChecker(pipewriter, false, CppCheckExecutor::executeCommand); + fileChecker.settings() = mSettings; + unsigned int resultOfCheck = 0; + + if (iFileSettings != mSettings.project.fileSettings.end()) { + resultOfCheck = fileChecker.check(*iFileSettings); + } else { + // Read file from a file + resultOfCheck = fileChecker.check(iFile->first); + } + + std::ostringstream oss; + oss << resultOfCheck; + pipewriter.writeEnd(oss.str()); + std::exit(EXIT_SUCCESS); + } + + close(pipes[1]); + rpipes.push_back(pipes[0]); + if (iFileSettings != mSettings.project.fileSettings.end()) { + childFile[pid] = iFileSettings->filename + ' ' + iFileSettings->cfg; + pipeFile[pipes[0]] = iFileSettings->filename + ' ' + iFileSettings->cfg; + ++iFileSettings; + } else { + childFile[pid] = iFile->first; + pipeFile[pipes[0]] = iFile->first; + ++iFile; + } + } + if (!rpipes.empty()) { + fd_set rfds; + FD_ZERO(&rfds); + for (std::list::const_iterator rp = rpipes.begin(); rp != rpipes.end(); ++rp) + FD_SET(*rp, &rfds); + struct timeval tv; // for every second polling of load average condition + tv.tv_sec = 1; + tv.tv_usec = 0; + int r = select(*std::max_element(rpipes.begin(), rpipes.end()) + 1, &rfds, nullptr, nullptr, &tv); + + if (r > 0) { + std::list::iterator rp = rpipes.begin(); + while (rp != rpipes.end()) { + if (FD_ISSET(*rp, &rfds)) { + int readRes = handleRead(*rp, result); + if (readRes == -1) { + std::size_t size = 0; + std::map::iterator p = pipeFile.find(*rp); + if (p != pipeFile.end()) { + std::string name = p->second; + pipeFile.erase(p); + std::map::const_iterator fs = mFiles.find(name); + if (fs != mFiles.end()) { + size = fs->second; + } + } + + fileCount++; + processedsize += size; + if (!mSettings.quiet) + CppCheckExecutor::reportStatus(fileCount, mFiles.size() + mSettings.project.fileSettings.size(), processedsize, totalfilesize); + + close(*rp); + rp = rpipes.erase(rp); + } else + ++rp; + } else + ++rp; + } + } + } + if (!childFile.empty()) { + int stat = 0; + pid_t child = waitpid(0, &stat, WNOHANG); + if (child > 0) { + std::string childname; + std::map::iterator c = childFile.find(child); + if (c != childFile.end()) { + childname = c->second; + childFile.erase(c); + } + + if (WIFEXITED(stat)) { + const int exitstatus = WEXITSTATUS(stat); + if (exitstatus != EXIT_SUCCESS) { + std::ostringstream oss; + oss << "Child process exited with " << exitstatus; + reportInternalChildErr(childname, oss.str()); + } + } else if (WIFSIGNALED(stat)) { + std::ostringstream oss; + oss << "Child process crashed with signal " << WTERMSIG(stat); + reportInternalChildErr(childname, oss.str()); + } + } + } + if (iFile == mFiles.end() && iFileSettings == mSettings.project.fileSettings.end() && rpipes.empty() && childFile.empty()) { + // All done + break; + } + } + + + return result; +} + +void ProcessExecutor::reportInternalChildErr(const std::string &childname, const std::string &msg) +{ + std::list locations; + locations.emplace_back(childname, 0, 0); + const ErrorMessage errmsg(locations, + emptyString, + Severity::error, + "Internal error: " + msg, + "cppcheckError", + Certainty::normal); + + if (!mSettings.nomsg.isSuppressed(errmsg.toSuppressionsErrorMessage())) + mErrorLogger.reportErr(errmsg); +} + +#endif // !WIN32 diff --git a/cli/processexecutor.h b/cli/processexecutor.h new file mode 100644 index 000000000..b8ed621a4 --- /dev/null +++ b/cli/processexecutor.h @@ -0,0 +1,72 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PROCESSEXECUTOR_H +#define PROCESSEXECUTOR_H + +#include "executor.h" + +#include +#include +#include + +class Settings; +class ErrorLogger; + +/// @addtogroup CLI +/// @{ + +/** + * This class will take a list of filenames and settings and check then + * all files using threads. + */ +class ProcessExecutor : public Executor { +public: + ProcessExecutor(const std::map &files, Settings &settings, ErrorLogger &errorLogger); + ProcessExecutor(const ProcessExecutor &) = delete; + ~ProcessExecutor(); + void operator=(const ProcessExecutor &) = delete; + + unsigned int check() override; + +private: + /** + * Read from the pipe, parse and handle what ever is in there. + *@return -1 in case of error + * 0 if there is nothing in the pipe to be read + * 1 if we did read something + */ + int handleRead(int rpipe, unsigned int &result); + + /** + * @brief Check load average condition + * @param nchildren - count of currently ran children + * @return true - if new process can be started + */ + bool checkLoadAverage(size_t nchildren); + + /** + * @brief Reports internal errors related to child processes + * @param msg The error message + */ + void reportInternalChildErr(const std::string &childname, const std::string &msg); +}; + +/// @} + +#endif // PROCESSEXECUTOR_H diff --git a/cli/threadexecutor.cpp b/cli/threadexecutor.cpp index 134155d41..ab5777450 100644 --- a/cli/threadexecutor.cpp +++ b/cli/threadexecutor.cpp @@ -29,379 +29,33 @@ #include #include #include -#include -#include - -#ifdef __SVR4 // Solaris -#include -#endif - -#ifdef THREADING_MODEL_FORK -#include "config.h" -#include "errortypes.h" - -#if defined(__linux__) -#include -#endif -#include -#include -#include -#include -#include -#include -#include - -// NOLINTNEXTLINE(misc-unused-using-decls) - required for FD_ZERO -using std::memset; -#endif - -#ifdef THREADING_MODEL_THREAD #include +#include +#include #include -#endif +#include +#include +#include +#include ThreadExecutor::ThreadExecutor(const std::map &files, Settings &settings, ErrorLogger &errorLogger) - : mFiles(files), mSettings(settings), mErrorLogger(errorLogger) + : Executor(files, settings, errorLogger) {} ThreadExecutor::~ThreadExecutor() {} -/////////////////////////////////////////////////////////////////////////////// -////// This code is for platforms that support fork() only //////////////////// -/////////////////////////////////////////////////////////////////////////////// - -#if defined(THREADING_MODEL_FORK) - -class PipeWriter : public ErrorLogger { -public: - enum PipeSignal {REPORT_OUT='1',REPORT_ERROR='2', REPORT_INFO='3', REPORT_VERIFICATION='4', CHILD_END='5'}; - - explicit PipeWriter(int pipe) : mWpipe(pipe) {} - - void reportOut(const std::string &outmsg, Color c) override { - writeToPipe(REPORT_OUT, ::toString(c) + outmsg + ::toString(Color::Reset)); - } - - void reportErr(const ErrorMessage &msg) override { - report(msg, MessageType::REPORT_ERROR); - } - - void reportInfo(const ErrorMessage &msg) override { - report(msg, MessageType::REPORT_INFO); - } - - void writeEnd(const std::string& str) { - writeToPipe(CHILD_END, str); - } - -private: - enum class MessageType {REPORT_ERROR, REPORT_INFO}; - - void report(const ErrorMessage &msg, MessageType msgType) { - PipeSignal pipeSignal; - switch (msgType) { - case MessageType::REPORT_ERROR: - pipeSignal = REPORT_ERROR; - break; - case MessageType::REPORT_INFO: - pipeSignal = REPORT_INFO; - break; - } - - writeToPipe(pipeSignal, msg.serialize()); - } - - void writeToPipe(PipeSignal type, const std::string &data) - { - unsigned int len = static_cast(data.length() + 1); - char *out = new char[len + 1 + sizeof(len)]; - out[0] = static_cast(type); - std::memcpy(&(out[1]), &len, sizeof(len)); - std::memcpy(&(out[1+sizeof(len)]), data.c_str(), len); - if (write(mWpipe, out, len + 1 + sizeof(len)) <= 0) { - delete[] out; - out = nullptr; - std::cerr << "#### ThreadExecutor::writeToPipe, Failed to write to pipe" << std::endl; - std::exit(EXIT_FAILURE); - } - - delete[] out; - } - - const int mWpipe; -}; - -int ThreadExecutor::handleRead(int rpipe, unsigned int &result) -{ - char type = 0; - if (read(rpipe, &type, 1) <= 0) { - if (errno == EAGAIN) - return 0; - - // need to increment so a missing pipe (i.e. premature exit of forked process) results in an error exitcode - ++result; - return -1; - } - - if (type != PipeWriter::REPORT_OUT && type != PipeWriter::REPORT_ERROR && type != PipeWriter::REPORT_INFO && type != PipeWriter::CHILD_END) { - std::cerr << "#### ThreadExecutor::handleRead error, type was:" << type << std::endl; - std::exit(EXIT_FAILURE); - } - - unsigned int len = 0; - if (read(rpipe, &len, sizeof(len)) <= 0) { - std::cerr << "#### ThreadExecutor::handleRead error, type was:" << type << std::endl; - std::exit(EXIT_FAILURE); - } - - // Don't rely on incoming data being null-terminated. - // Allocate +1 element and null-terminate the buffer. - char *buf = new char[len + 1]; - const ssize_t readIntoBuf = read(rpipe, buf, len); - if (readIntoBuf <= 0) { - std::cerr << "#### ThreadExecutor::handleRead error, type was:" << type << std::endl; - std::exit(EXIT_FAILURE); - } - buf[readIntoBuf] = 0; - - if (type == PipeWriter::REPORT_OUT) { - mErrorLogger.reportOut(buf); - } else if (type == PipeWriter::REPORT_ERROR || type == PipeWriter::REPORT_INFO) { - ErrorMessage msg; - try { - msg.deserialize(buf); - } catch (const InternalError& e) { - std::cerr << "#### ThreadExecutor::handleRead error, internal error:" << e.errorMessage << std::endl; - std::exit(EXIT_FAILURE); - } - - if (!mSettings.nomsg.isSuppressed(msg.toSuppressionsErrorMessage())) { - // Alert only about unique errors - std::string errmsg = msg.toString(mSettings.verbose); - if (std::find(mErrorList.begin(), mErrorList.end(), errmsg) == mErrorList.end()) { - mErrorList.emplace_back(errmsg); - if (type == PipeWriter::REPORT_ERROR) - mErrorLogger.reportErr(msg); - else - mErrorLogger.reportInfo(msg); - } - } - } else if (type == PipeWriter::CHILD_END) { - std::istringstream iss(buf); - unsigned int fileResult = 0; - iss >> fileResult; - result += fileResult; - delete[] buf; - return -1; - } - - delete[] buf; - return 1; -} - -bool ThreadExecutor::checkLoadAverage(size_t nchildren) -{ -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__HAIKU__) // getloadavg() is unsupported on Cygwin, Qnx, Haiku. - return true; -#else - if (!nchildren || !mSettings.loadAverage) { - return true; - } - - double sample(0); - if (getloadavg(&sample, 1) != 1) { - // disable load average checking on getloadavg error - return true; - } else if (sample < mSettings.loadAverage) { - return true; - } - return false; -#endif -} - -unsigned int ThreadExecutor::check() -{ - unsigned int fileCount = 0; - unsigned int result = 0; - - std::size_t totalfilesize = 0; - for (std::map::const_iterator i = mFiles.begin(); i != mFiles.end(); ++i) { - totalfilesize += i->second; - } - - std::list rpipes; - std::map childFile; - std::map pipeFile; - std::size_t processedsize = 0; - std::map::const_iterator iFile = mFiles.begin(); - std::list::const_iterator iFileSettings = mSettings.project.fileSettings.begin(); - for (;;) { - // Start a new child - size_t nchildren = childFile.size(); - if ((iFile != mFiles.end() || iFileSettings != mSettings.project.fileSettings.end()) && nchildren < mSettings.jobs && checkLoadAverage(nchildren)) { - int pipes[2]; - if (pipe(pipes) == -1) { - std::cerr << "#### ThreadExecutor::check, pipe() failed: "<< std::strerror(errno) << std::endl; - std::exit(EXIT_FAILURE); - } - - int flags = 0; - if ((flags = fcntl(pipes[0], F_GETFL, 0)) < 0) { - std::cerr << "#### ThreadExecutor::check, fcntl(F_GETFL) failed: "<< std::strerror(errno) << std::endl; - std::exit(EXIT_FAILURE); - } - - if (fcntl(pipes[0], F_SETFL, flags | O_NONBLOCK) < 0) { - std::cerr << "#### ThreadExecutor::check, fcntl(F_SETFL) failed: "<< std::strerror(errno) << std::endl; - std::exit(EXIT_FAILURE); - } - - pid_t pid = fork(); - if (pid < 0) { - // Error - std::cerr << "#### ThreadExecutor::check, Failed to create child process: "<< std::strerror(errno) << std::endl; - std::exit(EXIT_FAILURE); - } else if (pid == 0) { -#if defined(__linux__) - prctl(PR_SET_PDEATHSIG, SIGHUP); -#endif - close(pipes[0]); - - PipeWriter pipewriter(pipes[1]); - CppCheck fileChecker(pipewriter, false, CppCheckExecutor::executeCommand); - fileChecker.settings() = mSettings; - unsigned int resultOfCheck = 0; - - if (iFileSettings != mSettings.project.fileSettings.end()) { - resultOfCheck = fileChecker.check(*iFileSettings); - } else { - // Read file from a file - resultOfCheck = fileChecker.check(iFile->first); - } - - std::ostringstream oss; - oss << resultOfCheck; - pipewriter.writeEnd(oss.str()); - std::exit(EXIT_SUCCESS); - } - - close(pipes[1]); - rpipes.push_back(pipes[0]); - if (iFileSettings != mSettings.project.fileSettings.end()) { - childFile[pid] = iFileSettings->filename + ' ' + iFileSettings->cfg; - pipeFile[pipes[0]] = iFileSettings->filename + ' ' + iFileSettings->cfg; - ++iFileSettings; - } else { - childFile[pid] = iFile->first; - pipeFile[pipes[0]] = iFile->first; - ++iFile; - } - } - if (!rpipes.empty()) { - fd_set rfds; - FD_ZERO(&rfds); - for (std::list::const_iterator rp = rpipes.begin(); rp != rpipes.end(); ++rp) - FD_SET(*rp, &rfds); - struct timeval tv; // for every second polling of load average condition - tv.tv_sec = 1; - tv.tv_usec = 0; - int r = select(*std::max_element(rpipes.begin(), rpipes.end()) + 1, &rfds, nullptr, nullptr, &tv); - - if (r > 0) { - std::list::iterator rp = rpipes.begin(); - while (rp != rpipes.end()) { - if (FD_ISSET(*rp, &rfds)) { - int readRes = handleRead(*rp, result); - if (readRes == -1) { - std::size_t size = 0; - std::map::iterator p = pipeFile.find(*rp); - if (p != pipeFile.end()) { - std::string name = p->second; - pipeFile.erase(p); - std::map::const_iterator fs = mFiles.find(name); - if (fs != mFiles.end()) { - size = fs->second; - } - } - - fileCount++; - processedsize += size; - if (!mSettings.quiet) - CppCheckExecutor::reportStatus(fileCount, mFiles.size() + mSettings.project.fileSettings.size(), processedsize, totalfilesize); - - close(*rp); - rp = rpipes.erase(rp); - } else - ++rp; - } else - ++rp; - } - } - } - if (!childFile.empty()) { - int stat = 0; - pid_t child = waitpid(0, &stat, WNOHANG); - if (child > 0) { - std::string childname; - std::map::iterator c = childFile.find(child); - if (c != childFile.end()) { - childname = c->second; - childFile.erase(c); - } - - if (WIFEXITED(stat)) { - const int exitstatus = WEXITSTATUS(stat); - if (exitstatus != EXIT_SUCCESS) { - std::ostringstream oss; - oss << "Child process exited with " << exitstatus; - reportInternalChildErr(childname, oss.str()); - } - } else if (WIFSIGNALED(stat)) { - std::ostringstream oss; - oss << "Child process crashed with signal " << WTERMSIG(stat); - reportInternalChildErr(childname, oss.str()); - } - } - } - if (iFile == mFiles.end() && iFileSettings == mSettings.project.fileSettings.end() && rpipes.empty() && childFile.empty()) { - // All done - break; - } - } - - - return result; -} - -void ThreadExecutor::reportInternalChildErr(const std::string &childname, const std::string &msg) -{ - std::list locations; - locations.emplace_back(childname, 0, 0); - const ErrorMessage errmsg(locations, - emptyString, - Severity::error, - "Internal error: " + msg, - "cppcheckError", - Certainty::normal); - - if (!mSettings.nomsg.isSuppressed(errmsg.toSuppressionsErrorMessage())) - mErrorLogger.reportErr(errmsg); -} - -#elif defined(THREADING_MODEL_THREAD) - class ThreadExecutor::SyncLogForwarder : public ErrorLogger { public: - SyncLogForwarder(ThreadExecutor &threadExecutor) + explicit SyncLogForwarder(ThreadExecutor &threadExecutor) : mThreadExecutor(threadExecutor), mProcessedFiles(0), mTotalFiles(0), mProcessedSize(0), mTotalFileSize(0) { - mItNextFile = threadExecutor.mFiles.begin(); - mItNextFileSettings = threadExecutor.mSettings.project.fileSettings.begin(); + mItNextFile = mThreadExecutor.mFiles.begin(); + mItNextFileSettings = mThreadExecutor.mSettings.project.fileSettings.begin(); - mTotalFiles = threadExecutor.mFiles.size() + threadExecutor.mSettings.project.fileSettings.size(); - for (std::map::const_iterator i = threadExecutor.mFiles.begin(); i != threadExecutor.mFiles.end(); ++i) { + mTotalFiles = mThreadExecutor.mFiles.size() + mThreadExecutor.mSettings.project.fileSettings.size(); + for (std::map::const_iterator i = mThreadExecutor.mFiles.begin(); i != mThreadExecutor.mFiles.end(); ++i) { mTotalFileSize += i->second; } } @@ -541,8 +195,3 @@ unsigned int STDCALL ThreadExecutor::threadProc(SyncLogForwarder* logForwarder) } return result; } -#endif - -bool ThreadExecutor::isEnabled() { - return true; -} diff --git a/cli/threadexecutor.h b/cli/threadexecutor.h index 6bcf248ec..1637854bf 100644 --- a/cli/threadexecutor.h +++ b/cli/threadexecutor.h @@ -21,8 +21,9 @@ #include "config.h" +#include "executor.h" + #include -#include #include #include @@ -36,55 +37,18 @@ class ErrorLogger; * This class will take a list of filenames and settings and check then * all files using threads. */ -class ThreadExecutor { +class ThreadExecutor : public Executor { public: ThreadExecutor(const std::map &files, Settings &settings, ErrorLogger &errorLogger); ThreadExecutor(const ThreadExecutor &) = delete; ~ThreadExecutor(); void operator=(const ThreadExecutor &) = delete; - unsigned int check(); + + unsigned int check() override; private: - const std::map &mFiles; - Settings &mSettings; - ErrorLogger &mErrorLogger; - std::list mErrorList; - -#if defined(THREADING_MODEL_FORK) - - /** - * Read from the pipe, parse and handle what ever is in there. - *@return -1 in case of error - * 0 if there is nothing in the pipe to be read - * 1 if we did read something - */ - int handleRead(int rpipe, unsigned int &result); - - /** - * @brief Check load average condition - * @param nchildren - count of currently ran children - * @return true - if new process can be started - */ - bool checkLoadAverage(size_t nchildren); - - /** - * @brief Reports internal errors related to child processes - * @param msg The error message - */ - void reportInternalChildErr(const std::string &childname, const std::string &msg); - -#elif defined(THREADING_MODEL_THREAD) - class SyncLogForwarder; - static unsigned int STDCALL threadProc(SyncLogForwarder *logforwarder); - -#endif - -public: - /** - * @return true if support for threads exist. - */ - static bool isEnabled(); + static unsigned int STDCALL threadProc(SyncLogForwarder *logForwarder); }; /// @} diff --git a/cmake/findDependencies.cmake b/cmake/findDependencies.cmake index 14cbd235a..b9d5052e4 100644 --- a/cmake/findDependencies.cmake +++ b/cmake/findDependencies.cmake @@ -68,9 +68,7 @@ if (NOT USE_BUNDLED_TINYXML2) endif() endif() -if (USE_THREADS) - find_package(Threads REQUIRED) -endif() +find_package(Threads REQUIRED) if (USE_BOOST) find_package(Boost COMPONENTS container QUIET) diff --git a/cmake/printInfo.cmake b/cmake/printInfo.cmake index 159d65f36..85a955016 100644 --- a/cmake/printInfo.cmake +++ b/cmake/printInfo.cmake @@ -52,9 +52,7 @@ if (HAVE_RULES) endif() message( STATUS ) message( STATUS "USE_THREADS = ${USE_THREADS}" ) -if (USE_THREADS) - message( STATUS "CMAKE_THREAD_LIBS_INIT = ${CMAKE_THREAD_LIBS_INIT}" ) -endif() +message( STATUS "CMAKE_THREAD_LIBS_INIT = ${CMAKE_THREAD_LIBS_INIT}" ) if (NOT USE_MATCHCOMPILER_OPT MATCHES "Off") message( STATUS ) message( STATUS "PYTHON_VERSION_STRING = ${PYTHON_VERSION_STRING}" ) diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 232630992..39c3ff06b 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -28,6 +28,7 @@ #include "symboldatabase.h" #include "token.h" #include "tokenize.h" +#include "utils.h" #include "valueflow.h" #include "checkuninitvar.h" // CheckUninitVar::isVariableUsage diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index 0b30b0376..da2587cd4 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -30,6 +30,7 @@ #include "tokenize.h" #include "tokenlist.h" #include "utils.h" +#include "valueflow.h" #include #include diff --git a/lib/config.h b/lib/config.h index dbe3aa908..87451e2a9 100644 --- a/lib/config.h +++ b/lib/config.h @@ -112,6 +112,7 @@ static const std::string emptyString; #define STDCALL #elif ((defined(__GNUC__) || defined(__sun)) && !defined(__MINGW32__) && !defined(__CYGWIN__)) || defined(__CPPCHECK__) #define THREADING_MODEL_FORK +#define STDCALL #else #error "No threading model defined" #endif diff --git a/lib/ctu.cpp b/lib/ctu.cpp index 08b3472f7..06dce57bc 100644 --- a/lib/ctu.cpp +++ b/lib/ctu.cpp @@ -29,6 +29,7 @@ #include "tokenlist.h" #include "utils.h" +#include #include #include #include // back_inserter diff --git a/lib/ctu.h b/lib/ctu.h index 7379fc398..1ab2a2f17 100644 --- a/lib/ctu.h +++ b/lib/ctu.h @@ -28,7 +28,6 @@ #include "mathlib.h" #include "valueflow.h" -#include #include #include #include diff --git a/lib/path.cpp b/lib/path.cpp index dc1642e90..b76ebaa88 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -23,7 +23,6 @@ #include "utils.h" #include -#include #include #include diff --git a/lib/pathmatch.cpp b/lib/pathmatch.cpp index 61a6b7f44..4c56db8f6 100644 --- a/lib/pathmatch.cpp +++ b/lib/pathmatch.cpp @@ -21,8 +21,6 @@ #include "path.h" #include "utils.h" -#include -#include #include PathMatch::PathMatch(const std::vector &excludedPaths, bool caseSensitive) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9e3de5f74..962874bfe 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,9 +25,7 @@ if (BUILD_TESTS) if(tinyxml2_FOUND AND NOT USE_BUNDLED_TINYXML2) target_link_libraries(testrunner ${tinyxml2_LIBRARIES}) endif() - if (USE_THREADS) - target_link_libraries(testrunner ${CMAKE_THREAD_LIBS_INIT}) - endif() + target_link_libraries(testrunner ${CMAKE_THREAD_LIBS_INIT}) if (NOT CMAKE_DISABLE_PRECOMPILE_HEADERS) target_precompile_headers(testrunner PRIVATE precompiled.h) diff --git a/test/testprocessexecutor.cpp b/test/testprocessexecutor.cpp new file mode 100644 index 000000000..8fc5459db --- /dev/null +++ b/test/testprocessexecutor.cpp @@ -0,0 +1,142 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2022 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "processexecutor.h" +#include "settings.h" +#include "testsuite.h" +#include "testutils.h" + +#include +#include +#include +#include +#include +#include +#include + +class TestProcessExecutor : public TestFixture { +public: + TestProcessExecutor() : TestFixture("TestProcessExecutor") {} + +private: + Settings settings; + + /** + * Execute check using n jobs for y files which are have + * identical data, given within data. + */ + void check(unsigned int jobs, int files, int result, const std::string &data) { + errout.str(""); + output.str(""); + + std::map filemap; + for (int i = 1; i <= files; ++i) { + std::ostringstream oss; + oss << "file_" << i << ".cpp"; + filemap[oss.str()] = data.size(); + } + + settings.jobs = jobs; + ProcessExecutor executor(filemap, settings, *this); + std::vector scopedfiles; + scopedfiles.reserve(filemap.size()); + for (std::map::const_iterator i = filemap.begin(); i != filemap.end(); ++i) + scopedfiles.emplace_back(i->first, data); + + ASSERT_EQUALS(result, executor.check()); + } + + void run() override { +#if !defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) + LOAD_LIB_2(settings.library, "std.cfg"); + + TEST_CASE(deadlock_with_many_errors); + TEST_CASE(many_threads); + TEST_CASE(no_errors_more_files); + TEST_CASE(no_errors_less_files); + TEST_CASE(no_errors_equal_amount_files); + TEST_CASE(one_error_less_files); + TEST_CASE(one_error_several_files); +#endif // !WIN32 + } + + void deadlock_with_many_errors() { + std::ostringstream oss; + oss << "int main()\n" + << "{\n"; + for (int i = 0; i < 500; i++) + oss << " {char *a = malloc(10);}\n"; + + oss << " return 0;\n" + << "}\n"; + check(2, 3, 3, oss.str()); + } + + void many_threads() { + check(16, 100, 100, + "int main()\n" + "{\n" + " char *a = malloc(10);\n" + " return 0;\n" + "}"); + } + + void no_errors_more_files() { + check(2, 3, 0, + "int main()\n" + "{\n" + " return 0;\n" + "}"); + } + + void no_errors_less_files() { + check(2, 1, 0, + "int main()\n" + "{\n" + " return 0;\n" + "}"); + } + + void no_errors_equal_amount_files() { + check(2, 2, 0, + "int main()\n" + "{\n" + " return 0;\n" + "}"); + } + + void one_error_less_files() { + check(2, 1, 1, + "int main()\n" + "{\n" + " {char *a = malloc(10);}\n" + " return 0;\n" + "}"); + } + + void one_error_several_files() { + check(2, 20, 20, + "int main()\n" + "{\n" + " {char *a = malloc(10);}\n" + " return 0;\n" + "}"); + } +}; + +REGISTER_TEST(TestProcessExecutor) diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index 10994701a..14bc3a539 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -27,6 +27,8 @@ + + @@ -64,6 +66,7 @@ + diff --git a/tools/dmake.cpp b/tools/dmake.cpp index a7d0df3b4..4c03bab41 100644 --- a/tools/dmake.cpp +++ b/tools/dmake.cpp @@ -315,6 +315,8 @@ int main(int argc, char **argv) << " endif # !CPPCHK_GLIBCXX_DEBUG\n" << " endif # GNU/kFreeBSD\n" << "\n" + << " LDFLAGS=-pthread\n" + << "\n" << "endif # WINNT\n" << "\n"; @@ -413,7 +415,7 @@ int main(int argc, char **argv) fout << "cppcheck: $(LIBOBJ) $(CLIOBJ) $(EXTOBJ)\n"; fout << "\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LIBS) $(LDFLAGS) $(RDYNAMIC)\n\n"; fout << "all:\tcppcheck testrunner\n\n"; - fout << "testrunner: $(TESTOBJ) $(LIBOBJ) $(EXTOBJ) cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/filelister.o\n"; + fout << "testrunner: $(TESTOBJ) $(LIBOBJ) $(EXTOBJ) cli/executor.o cli/processexecutor.o cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/filelister.o\n"; fout << "\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LIBS) $(LDFLAGS) $(RDYNAMIC)\n\n"; fout << "test:\tall\n"; fout << "\t./testrunner\n\n";