2022-07-08 16:42:57 +02:00
/*
* Cppcheck - A tool for static C / C + + code analysis
2023-01-28 10:16:34 +01:00
* Copyright ( C ) 2007 - 2023 Cppcheck team .
2022-07-08 16:42:57 +02:00
*
* 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 < http : //www.gnu.org/licenses/>.
*/
# include "processexecutor.h"
2022-07-13 13:46:03 +02:00
# if !defined(WIN32) && !defined(__MINGW32__)
2022-07-08 16:42:57 +02:00
# 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 <algorithm>
2022-10-16 13:46:26 +02:00
# include <numeric>
2023-04-28 12:41:53 +02:00
# include <cassert>
2022-07-08 16:42:57 +02:00
# include <cerrno>
# include <csignal>
# include <cstdlib>
# include <cstring>
# include <functional>
# include <iostream>
# include <list>
2022-09-16 07:15:49 +02:00
# include <sstream> // IWYU pragma: keep
2022-07-08 16:42:57 +02:00
# include <sys/select.h>
# include <sys/wait.h>
# include <unistd.h>
2023-03-02 21:50:14 +01:00
# include <utility>
2022-10-16 13:46:26 +02:00
# include <fcntl.h>
2022-07-08 16:42:57 +02:00
# ifdef __SVR4 // Solaris
# include <sys/loadavg.h>
# endif
# if defined(__linux__)
# include <sys/prctl.h>
# endif
2023-04-30 07:33:19 +02:00
enum class Color ;
2022-07-08 16:42:57 +02:00
// NOLINTNEXTLINE(misc-unused-using-decls) - required for FD_ZERO
using std : : memset ;
ProcessExecutor : : ProcessExecutor ( const std : : map < std : : string , std : : size_t > & files , Settings & settings , ErrorLogger & errorLogger )
: Executor ( files , settings , errorLogger )
2023-04-28 12:41:53 +02:00
{
assert ( mSettings . jobs > 1 ) ;
}
2022-07-08 16:42:57 +02:00
ProcessExecutor : : ~ ProcessExecutor ( )
{ }
class PipeWriter : public ErrorLogger {
public :
2023-04-28 16:02:41 +02:00
enum PipeSignal { REPORT_OUT = ' 1 ' , REPORT_ERROR = ' 2 ' , CHILD_END = ' 5 ' } ;
2022-07-08 16:42:57 +02:00
explicit PipeWriter ( int pipe ) : mWpipe ( pipe ) { }
void reportOut ( const std : : string & outmsg , Color c ) override {
2023-04-08 18:06:38 +02:00
writeToPipe ( REPORT_OUT , static_cast < char > ( c ) + outmsg ) ;
2022-07-08 16:42:57 +02:00
}
void reportErr ( const ErrorMessage & msg ) override {
2023-03-04 17:29:34 +01:00
writeToPipe ( REPORT_ERROR , msg . serialize ( ) ) ;
2022-07-08 16:42:57 +02:00
}
2022-09-08 20:01:41 +02:00
void writeEnd ( const std : : string & str ) const {
2022-07-08 16:42:57 +02:00
writeToPipe ( CHILD_END , str ) ;
}
private :
2023-03-07 12:25:49 +01:00
// TODO: how to log file name in error?
2022-09-08 20:01:41 +02:00
void writeToPipe ( PipeSignal type , const std : : string & data ) const
2022-07-08 16:42:57 +02:00
{
unsigned int len = static_cast < unsigned int > ( data . length ( ) + 1 ) ;
char * out = new char [ len + 1 + sizeof ( len ) ] ;
out [ 0 ] = static_cast < char > ( type ) ;
std : : memcpy ( & ( out [ 1 ] ) , & len , sizeof ( len ) ) ;
std : : memcpy ( & ( out [ 1 + sizeof ( len ) ] ) , data . c_str ( ) , len ) ;
2023-03-07 12:25:49 +01:00
std : : size_t bytes_to_write = len + 1 + sizeof ( len ) ;
ssize_t bytes_written = write ( mWpipe , out , len + 1 + sizeof ( len ) ) ;
if ( bytes_written < = 0 ) {
const int err = errno ;
delete [ ] out ;
out = nullptr ;
std : : cerr < < " #### ThreadExecutor::writeToPipe() error for type " < < type < < " : " < < std : : strerror ( err ) < < std : : endl ;
std : : exit ( EXIT_FAILURE ) ;
}
// TODO: write until everything is written
if ( bytes_written ! = bytes_to_write ) {
2022-07-08 16:42:57 +02:00
delete [ ] out ;
out = nullptr ;
2023-03-07 12:25:49 +01:00
std : : cerr < < " #### ThreadExecutor::writeToPipe() error for type " < < type < < " : insufficient data written (expected: " < < bytes_to_write < < " / got: " < < bytes_written < < " ) " < < std : : endl ;
2022-07-08 16:42:57 +02:00
std : : exit ( EXIT_FAILURE ) ;
}
delete [ ] out ;
}
const int mWpipe ;
} ;
2023-03-07 12:25:49 +01:00
bool ProcessExecutor : : handleRead ( int rpipe , unsigned int & result , const std : : string & filename )
2022-07-08 16:42:57 +02:00
{
2023-03-07 12:25:49 +01:00
std : : size_t bytes_to_read ;
ssize_t bytes_read ;
2022-07-08 16:42:57 +02:00
char type = 0 ;
2023-03-07 12:25:49 +01:00
bytes_to_read = sizeof ( char ) ;
bytes_read = read ( rpipe , & type , bytes_to_read ) ;
if ( bytes_read < = 0 ) {
2022-07-08 16:42:57 +02:00
if ( errno = = EAGAIN )
2023-03-07 12:25:49 +01:00
return true ;
// TODO: log details about failure
2022-07-08 16:42:57 +02:00
// need to increment so a missing pipe (i.e. premature exit of forked process) results in an error exitcode
+ + result ;
2023-03-07 12:25:49 +01:00
return false ;
}
if ( bytes_read ! = bytes_to_read ) {
std : : cerr < < " #### ThreadExecutor::handleRead( " < < filename < < " ) error (type): insufficient data read (expected: " < < bytes_to_read < < " / got: " < < bytes_read < < " ) " < < std : : endl ;
std : : exit ( EXIT_FAILURE ) ;
2022-07-08 16:42:57 +02:00
}
2023-03-04 17:29:34 +01:00
if ( type ! = PipeWriter : : REPORT_OUT & & type ! = PipeWriter : : REPORT_ERROR & & type ! = PipeWriter : : CHILD_END ) {
2023-03-07 12:25:49 +01:00
std : : cerr < < " #### ThreadExecutor::handleRead( " < < filename < < " ) invalid type " < < int ( type ) < < std : : endl ;
2022-07-08 16:42:57 +02:00
std : : exit ( EXIT_FAILURE ) ;
}
unsigned int len = 0 ;
2023-03-07 12:25:49 +01:00
bytes_to_read = sizeof ( len ) ;
bytes_read = read ( rpipe , & len , bytes_to_read ) ;
if ( bytes_read < = 0 ) {
const int err = errno ;
std : : cerr < < " #### ThreadExecutor::handleRead( " < < filename < < " ) error (len) for type " < < int ( type ) < < " : " < < std : : strerror ( err ) < < std : : endl ;
std : : exit ( EXIT_FAILURE ) ;
}
if ( bytes_read ! = bytes_to_read ) {
std : : cerr < < " #### ThreadExecutor::handleRead( " < < filename < < " ) error (len) for type " < < int ( type ) < < " : insufficient data read (expected: " < < bytes_to_read < < " / got: " < < bytes_read < < " ) " < < std : : endl ;
2022-07-08 16:42:57 +02:00
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 ] ;
2023-03-07 12:25:49 +01:00
char * data_start = buf ;
bytes_to_read = len ;
do {
bytes_read = read ( rpipe , data_start , bytes_to_read ) ;
if ( bytes_read < = 0 ) {
const int err = errno ;
std : : cerr < < " #### ThreadExecutor::handleRead( " < < filename < < " ) error (buf) for type " < < int ( type ) < < " : " < < std : : strerror ( err ) < < std : : endl ;
std : : exit ( EXIT_FAILURE ) ;
}
bytes_to_read - = bytes_read ;
data_start + = bytes_read ;
} while ( bytes_to_read ! = 0 ) ;
buf [ len ] = 0 ;
2022-07-08 16:42:57 +02:00
2023-03-07 12:25:49 +01:00
bool res = true ;
2022-07-08 16:42:57 +02:00
if ( type = = PipeWriter : : REPORT_OUT ) {
2023-04-08 18:06:38 +02:00
// the first charcater is the color
const Color c = static_cast < Color > ( buf [ 0 ] ) ;
mErrorLogger . reportOut ( buf + 1 , c ) ;
2023-03-04 17:29:34 +01:00
} else if ( type = = PipeWriter : : REPORT_ERROR ) {
2022-07-08 16:42:57 +02:00
ErrorMessage msg ;
try {
2022-12-07 18:00:45 +01:00
msg . deserialize ( buf ) ;
2022-07-08 16:42:57 +02:00
} catch ( const InternalError & e ) {
2023-03-07 12:25:49 +01:00
std : : cerr < < " #### ThreadExecutor::handleRead( " < < filename < < " ) internal error: " < < e . errorMessage < < std : : endl ;
2022-07-08 16:42:57 +02:00
std : : exit ( EXIT_FAILURE ) ;
}
2023-03-04 17:29:34 +01:00
if ( hasToLog ( msg ) )
mErrorLogger . reportErr ( msg ) ;
2022-07-08 16:42:57 +02:00
} else if ( type = = PipeWriter : : CHILD_END ) {
2023-03-07 12:25:49 +01:00
result + = std : : stoi ( buf ) ;
res = false ;
2022-07-08 16:42:57 +02:00
}
delete [ ] buf ;
2023-03-07 12:25:49 +01:00
return res ;
2022-07-08 16:42:57 +02:00
}
bool ProcessExecutor : : checkLoadAverage ( size_t nchildren )
{
2022-07-30 14:56:35 +02:00
# if defined(__QNX__) || defined(__HAIKU__) // getloadavg() is unsupported on Qnx, Haiku.
2022-07-08 16:42:57 +02:00
( 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 ;
2022-12-30 15:13:47 +01:00
const std : : size_t totalfilesize = std : : accumulate ( mFiles . cbegin ( ) , mFiles . cend ( ) , std : : size_t ( 0 ) , [ ] ( std : : size_t v , const std : : pair < std : : string , std : : size_t > & p ) {
2022-10-16 13:46:26 +02:00
return v + p . second ;
} ) ;
2022-07-08 16:42:57 +02:00
std : : list < int > rpipes ;
std : : map < pid_t , std : : string > childFile ;
std : : map < int , std : : string > pipeFile ;
std : : size_t processedsize = 0 ;
2022-12-20 20:32:16 +01:00
std : : map < std : : string , std : : size_t > : : const_iterator iFile = mFiles . cbegin ( ) ;
std : : list < ImportProject : : FileSettings > : : const_iterator iFileSettings = mSettings . project . fileSettings . cbegin ( ) ;
2022-07-08 16:42:57 +02:00
for ( ; ; ) {
// Start a new child
2022-10-02 07:12:40 +02:00
const size_t nchildren = childFile . size ( ) ;
2022-12-20 20:32:16 +01:00
if ( ( iFile ! = mFiles . cend ( ) | | iFileSettings ! = mSettings . project . fileSettings . cend ( ) ) & & nchildren < mSettings . jobs & & checkLoadAverage ( nchildren ) ) {
2022-07-08 16:42:57 +02:00
int pipes [ 2 ] ;
if ( pipe ( pipes ) = = - 1 ) {
std : : cerr < < " #### ThreadExecutor::check, pipe() failed: " < < std : : strerror ( errno ) < < std : : endl ;
std : : exit ( EXIT_FAILURE ) ;
}
2022-10-02 07:12:40 +02:00
const int flags = fcntl ( pipes [ 0 ] , F_GETFL , 0 ) ;
2022-07-28 22:53:59 +02:00
if ( flags < 0 ) {
2022-07-08 16:42:57 +02:00
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 ) ;
}
2022-10-02 07:12:40 +02:00
const pid_t pid = fork ( ) ;
2022-07-08 16:42:57 +02:00
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 ) ;
}
2023-01-16 22:05:33 +01:00
pipewriter . writeEnd ( std : : to_string ( resultOfCheck ) ) ;
2022-07-08 16:42:57 +02:00
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 ) ;
2022-12-20 20:32:16 +01:00
for ( std : : list < int > : : const_iterator rp = rpipes . cbegin ( ) ; rp ! = rpipes . cend ( ) ; + + rp )
2022-07-08 16:42:57 +02:00
FD_SET ( * rp , & rfds ) ;
struct timeval tv ; // for every second polling of load average condition
tv . tv_sec = 1 ;
tv . tv_usec = 0 ;
2022-12-30 15:13:47 +01:00
const int r = select ( * std : : max_element ( rpipes . cbegin ( ) , rpipes . cend ( ) ) + 1 , & rfds , nullptr , nullptr , & tv ) ;
2022-07-08 16:42:57 +02:00
if ( r > 0 ) {
std : : list < int > : : iterator rp = rpipes . begin ( ) ;
while ( rp ! = rpipes . end ( ) ) {
if ( FD_ISSET ( * rp , & rfds ) ) {
2023-03-07 12:25:49 +01:00
std : : string name ;
const std : : map < int , std : : string > : : iterator p = pipeFile . find ( * rp ) ;
if ( p ! = pipeFile . end ( ) ) {
name = p - > second ;
}
const bool readRes = handleRead ( * rp , result , name ) ;
if ( ! readRes ) {
2022-07-08 16:42:57 +02:00
std : : size_t size = 0 ;
if ( p ! = pipeFile . end ( ) ) {
pipeFile . erase ( p ) ;
2022-10-02 07:12:40 +02:00
const std : : map < std : : string , std : : size_t > : : const_iterator fs = mFiles . find ( name ) ;
2022-07-08 16:42:57 +02:00
if ( fs ! = mFiles . end ( ) ) {
size = fs - > second ;
}
}
fileCount + + ;
processedsize + = size ;
if ( ! mSettings . quiet )
2023-04-08 18:06:38 +02:00
Executor : : reportStatus ( fileCount , mFiles . size ( ) + mSettings . project . fileSettings . size ( ) , processedsize , totalfilesize ) ;
2022-07-08 16:42:57 +02:00
close ( * rp ) ;
rp = rpipes . erase ( rp ) ;
} else
+ + rp ;
} else
+ + rp ;
}
}
}
if ( ! childFile . empty ( ) ) {
int stat = 0 ;
2022-10-02 07:12:40 +02:00
const pid_t child = waitpid ( 0 , & stat , WNOHANG ) ;
2022-07-08 16:42:57 +02:00
if ( child > 0 ) {
std : : string childname ;
2022-10-02 07:12:40 +02:00
const std : : map < pid_t , std : : string > : : iterator c = childFile . find ( child ) ;
2022-07-08 16:42:57 +02:00
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 < ErrorMessage : : FileLocation > locations ;
locations . emplace_back ( childname , 0 , 0 ) ;
const ErrorMessage errmsg ( locations ,
emptyString ,
Severity : : error ,
" Internal error: " + msg ,
" cppcheckError " ,
Certainty : : normal ) ;
2023-03-03 18:37:09 +01:00
if ( ! mSettings . nomsg . isSuppressed ( errmsg ) )
2022-07-08 16:42:57 +02:00
mErrorLogger . reportErr ( errmsg ) ;
}
# endif // !WIN32