diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 81cb54407a9..f6065170ae7 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -418,6 +418,22 @@ jobs: run: | make -j$(nproc) checkCWEEntries validateXML + - name: Test Signalhandler + run: | + cmake -S . -B cmake.output.signal -G "Unix Makefiles" -DBUILD_TESTS=On + cmake --build cmake.output.signal --target test-signalhandler -- -j$(nproc) + cp cmake.output.signal/bin/test-s* . + python3 -m pytest -Werror --strict-markers -vv test/signal/test-signalhandler.py + + # no unix backtrace support on MacOs + - name: Test Stacktrace + if: contains(matrix.os, 'ubuntu') + run: | + cmake -S . -B cmake.output.signal -G "Unix Makefiles" -DBUILD_TESTS=On + cmake --build cmake.output.signal --target test-stacktrace -- -j$(nproc) + cp cmake.output.signal/bin/test-s* . + python3 -m pytest -Werror --strict-markers -vv test/signal/test-stacktrace.py + # TODO: move to scriptcheck.yml so these are tested with all Python versions? - name: Test addons run: | diff --git a/Makefile b/Makefile index efe125d3d5b..22f1823feea 100644 --- a/Makefile +++ b/Makefile @@ -258,11 +258,11 @@ EXTOBJ = externals/simplecpp/simplecpp.o \ CLIOBJ = cli/cmdlineparser.o \ cli/cppcheckexecutor.o \ cli/cppcheckexecutorseh.o \ - cli/cppcheckexecutorsig.o \ cli/executor.o \ cli/filelister.o \ cli/main.o \ cli/processexecutor.o \ + cli/signalhandler.o \ cli/singleexecutor.o \ cli/stacktrace.o \ cli/threadexecutor.o @@ -348,7 +348,7 @@ cppcheck: $(EXTOBJ) $(LIBOBJ) $(CLIOBJ) all: cppcheck testrunner -testrunner: $(EXTOBJ) $(TESTOBJ) $(LIBOBJ) cli/executor.o cli/processexecutor.o cli/singleexecutor.o cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/cppcheckexecutorseh.o cli/cppcheckexecutorsig.o cli/stacktrace.o cli/filelister.o +testrunner: $(EXTOBJ) $(TESTOBJ) $(LIBOBJ) cli/executor.o cli/processexecutor.o cli/singleexecutor.o cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/cppcheckexecutorseh.o cli/signalhandler.o cli/stacktrace.o cli/filelister.o $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LIBS) $(LDFLAGS) $(RDYNAMIC) test: all @@ -649,15 +649,12 @@ $(libcppdir)/vfvalue.o: lib/vfvalue.cpp lib/config.h lib/errortypes.h lib/mathli cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/filelister.h externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h lib/xml.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cmdlineparser.cpp -cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/cppcheckexecutorseh.h cli/cppcheckexecutorsig.h cli/executor.h cli/processexecutor.h cli/singleexecutor.h cli/threadexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkersreport.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h +cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/cppcheckexecutorseh.h cli/executor.h cli/processexecutor.h cli/signalhandler.h cli/singleexecutor.h cli/threadexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkersreport.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutor.cpp cli/cppcheckexecutorseh.o: cli/cppcheckexecutorseh.cpp cli/cppcheckexecutor.h cli/cppcheckexecutorseh.h lib/config.h lib/filesettings.h lib/platform.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutorseh.cpp -cli/cppcheckexecutorsig.o: cli/cppcheckexecutorsig.cpp cli/cppcheckexecutor.h cli/cppcheckexecutorsig.h cli/stacktrace.h lib/config.h lib/filesettings.h lib/platform.h lib/standards.h lib/utils.h - $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutorsig.cpp - cli/executor.o: cli/executor.cpp cli/executor.h lib/addoninfo.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/executor.cpp @@ -670,6 +667,9 @@ cli/main.o: cli/main.cpp cli/cppcheckexecutor.h lib/config.h lib/errortypes.h li cli/processexecutor.o: cli/processexecutor.cpp cli/executor.h cli/processexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.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) $(CXXFLAGS) -c -o $@ cli/processexecutor.cpp +cli/signalhandler.o: cli/signalhandler.cpp cli/signalhandler.h cli/stacktrace.h lib/config.h + $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/signalhandler.cpp + cli/singleexecutor.o: cli/singleexecutor.cpp cli/executor.h cli/singleexecutor.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.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) $(CXXFLAGS) -c -o $@ cli/singleexecutor.cpp diff --git a/cli/cli.vcxproj b/cli/cli.vcxproj index de4c330f72c..f532fa46e7d 100644 --- a/cli/cli.vcxproj +++ b/cli/cli.vcxproj @@ -224,10 +224,10 @@ - + @@ -241,7 +241,6 @@ - Create Create @@ -251,6 +250,7 @@ + diff --git a/cli/cli.vcxproj.filters b/cli/cli.vcxproj.filters index 2b107b060f4..2320255e6b5 100644 --- a/cli/cli.vcxproj.filters +++ b/cli/cli.vcxproj.filters @@ -26,7 +26,7 @@ Header Files - + Header Files @@ -58,7 +58,7 @@ Source Files - + Source Files diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index a86a4717b6f..1d9f5b4972e 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -53,7 +53,7 @@ #include #ifdef USE_UNIX_SIGNAL_HANDLING -#include "cppcheckexecutorsig.h" +#include "signalhandler.h" #endif #ifdef USE_WINDOWS_SEH @@ -207,7 +207,7 @@ int CppCheckExecutor::check_wrapper(const Settings& settings) return check_wrapper_seh(*this, &CppCheckExecutor::check_internal, settings); #elif defined(USE_UNIX_SIGNAL_HANDLING) if (settings.exceptionHandling) - return check_wrapper_sig(*this, &CppCheckExecutor::check_internal, settings); + register_signal_handler(); #endif return check_internal(settings); } @@ -443,8 +443,12 @@ void StdLogger::reportErr(const ErrorMessage &msg) void CppCheckExecutor::setExceptionOutput(FILE* exceptionOutput) { mExceptionOutput = exceptionOutput; +#if defined(USE_UNIX_SIGNAL_HANDLING) + set_signal_handler_output(mExceptionOutput); +#endif } +// cppcheck-suppress unusedFunction - only used by USE_WINDOWS_SEH code FILE* CppCheckExecutor::getExceptionOutput() { return mExceptionOutput; diff --git a/cli/cppcheckexecutorsig.cpp b/cli/signalhandler.cpp similarity index 93% rename from cli/cppcheckexecutorsig.cpp rename to cli/signalhandler.cpp index 45c9867c752..2043e5f2990 100644 --- a/cli/cppcheckexecutorsig.cpp +++ b/cli/signalhandler.cpp @@ -16,12 +16,10 @@ * along with this program. If not, see . */ -#include "cppcheckexecutorsig.h" +#include "signalhandler.h" #if defined(USE_UNIX_SIGNAL_HANDLING) -#include "cppcheckexecutor.h" - #ifdef USE_UNIX_BACKTRACE_SUPPORT #include "stacktrace.h" #endif @@ -57,6 +55,12 @@ static constexpr size_t MYSTACKSIZE = 16*1024+SIGSTKSZ; // wild guess about a re #endif static char mytstack[MYSTACKSIZE]= {0}; // alternative stack for signal handler static bool bStackBelowHeap=false; // lame attempt to locate heap vs. stack address space. See CppCheckExecutor::check_wrapper() +static FILE* signalOutput = stdout; + +void set_signal_handler_output(FILE* f) +{ + signalOutput = f; +} /** * \param[in] ptr address to be examined. @@ -121,27 +125,21 @@ static void CppcheckSignalHandler(int signo, siginfo_t * info, void * context) const Signalmap_t::const_iterator it=listofsignals.find(signo); const char * const signame = (it==listofsignals.end()) ? "unknown" : it->second.c_str(); -#ifdef USE_UNIX_BACKTRACE_SUPPORT - bool lowMem=false; // was low-memory condition detected? Be careful then! Avoid allocating much more memory then. -#endif bool unexpectedSignal=true; // unexpected indicates program failure bool terminate=true; // exit process/thread const bool isAddressOnStack = IsAddressOnStack(info->si_addr); - FILE* output = CppCheckExecutor::getExceptionOutput(); + FILE * const output = signalOutput; switch (signo) { case SIGABRT: fputs("Internal error: cppcheck received signal ", output); fputs(signame, output); fputs( #ifdef NDEBUG - " - out of memory?\n", + " - abort\n", #else - " - out of memory or assertion?\n", + " - abort or assertion\n", #endif output); -#ifdef USE_UNIX_BACKTRACE_SUPPORT - lowMem=true; // educated guess -#endif break; case SIGBUS: fputs("Internal error: cppcheck received signal ", output); @@ -281,7 +279,9 @@ static void CppcheckSignalHandler(int signo, siginfo_t * info, void * context) break; } #ifdef USE_UNIX_BACKTRACE_SUPPORT - print_stacktrace(output, true, -1, lowMem); + // flush otherwise the trace might be printed earlier + fflush(output); + print_stacktrace(output, 1, true, -1, true); #endif if (unexpectedSignal) { fputs("\nPlease report this to the cppcheck developers!\n", output); @@ -298,8 +298,10 @@ static void CppcheckSignalHandler(int signo, siginfo_t * info, void * context) } } -int check_wrapper_sig(CppCheckExecutor& executor, int (CppCheckExecutor::*f)(const Settings&) const, const Settings& settings) +void register_signal_handler() { + FILE * const output = signalOutput; + // determine stack vs. heap char stackVariable; char *heapVariable=static_cast(malloc(1)); @@ -311,7 +313,11 @@ int check_wrapper_sig(CppCheckExecutor& executor, int (CppCheckExecutor::*f)(con segv_stack.ss_sp = mytstack; segv_stack.ss_flags = 0; segv_stack.ss_size = MYSTACKSIZE; - sigaltstack(&segv_stack, nullptr); + if (sigaltstack(&segv_stack, nullptr) != 0) { + // TODO: log errno + fputs("could not set alternate signal stack context.\n", output); + std::exit(EXIT_FAILURE); + } // install signal handler struct sigaction act; @@ -321,7 +327,6 @@ int check_wrapper_sig(CppCheckExecutor& executor, int (CppCheckExecutor::*f)(con for (std::map::const_iterator sig=listofsignals.cbegin(); sig!=listofsignals.cend(); ++sig) { sigaction(sig->first, &act, nullptr); } - return (executor.*f)(settings); } #endif diff --git a/cli/cppcheckexecutorsig.h b/cli/signalhandler.h similarity index 73% rename from cli/cppcheckexecutorsig.h rename to cli/signalhandler.h index b29ba4c2393..b900de6991c 100644 --- a/cli/cppcheckexecutorsig.h +++ b/cli/signalhandler.h @@ -16,18 +16,20 @@ * along with this program. If not, see . */ -#ifndef CPPCHECKEXECUTORSIG_H -#define CPPCHECKEXECUTORSIG_H +#ifndef SIGNALHANDLER_H +#define SIGNALHANDLER_H #include "config.h" #if defined(USE_UNIX_SIGNAL_HANDLING) -class CppCheckExecutor; -class Settings; +/** + * @param f Output file + */ +void set_signal_handler_output(FILE* f); -int check_wrapper_sig(CppCheckExecutor& executor, int (CppCheckExecutor::*f)(const Settings&) const, const Settings& settings); +void register_signal_handler(); -#endif // CPPCHECKEXECUTORSIG_H +#endif // USE_UNIX_SIGNAL_HANDLING -#endif // CPPCHECKEXECUTORSIG_H +#endif // SIGNALHANDLER_H diff --git a/cli/stacktrace.cpp b/cli/stacktrace.cpp index 40a350d28fc..3e84a7d2809 100644 --- a/cli/stacktrace.cpp +++ b/cli/stacktrace.cpp @@ -28,69 +28,74 @@ #include #include -void print_stacktrace(FILE* output, bool demangling, int maxdepth, bool lowMem) +void print_stacktrace(FILE* output, int start_idx, bool demangling, int maxdepth, bool omit_above_own) { // 32 vs. 64bit #define ADDRESSDISPLAYLENGTH ((sizeof(long)==8)?12:8) - const int fd = fileno(output); void *callstackArray[32]= {nullptr}; // the less resources the better... const int currentdepth = backtrace(callstackArray, (int)getArrayLength(callstackArray)); - constexpr int offset=2; // some entries on top are within our own exception handling code or libc + // set offset to 1 to omit the printing function itself + int offset=start_idx+1; // some entries on top are within our own exception handling code or libc if (maxdepth<0) maxdepth=currentdepth-offset; else maxdepth = std::min(maxdepth, currentdepth); - if (lowMem) { - fputs("Callstack (symbols only):\n", output); - backtrace_symbols_fd(callstackArray+offset, maxdepth, fd); - } else { - char **symbolStringList = backtrace_symbols(callstackArray, currentdepth); - if (symbolStringList) { - fputs("Callstack:\n", output); - char demangle_buffer[2048]= {0}; - for (int i = offset; i < maxdepth; ++i) { - const char * const symbolString = symbolStringList[i]; - char * realnameString = nullptr; - const char * const firstBracketName = strchr(symbolString, '('); - const char * const firstBracketAddress = strchr(symbolString, '['); - const char * const secondBracketAddress = strchr(firstBracketAddress, ']'); - const char * const beginAddress = firstBracketAddress+3; - const int addressLen = int(secondBracketAddress-beginAddress); - const int padLen = int(ADDRESSDISPLAYLENGTH-addressLen); - if (demangling && firstBracketName) { - const char * const plus = strchr(firstBracketName, '+'); - if (plus && (plus>(firstBracketName+1))) { - char input_buffer[1024]= {0}; - strncpy(input_buffer, firstBracketName+1, plus-firstBracketName-1); - size_t length = getArrayLength(demangle_buffer); - int status=0; - // We're violating the specification - passing stack address instead of malloc'ed heap. - // Benefit is that no further heap is required, while there is sufficient stack... - realnameString = abi::__cxa_demangle(input_buffer, demangle_buffer, &length, &status); // non-NULL on success - } - } - const int ordinal=i-offset; - fprintf(output, "#%-2d 0x", - ordinal); - if (padLen>0) - fprintf(output, "%0*d", - padLen, 0); - if (realnameString) { - fprintf(output, "%.*s in %s\n", - (int)(secondBracketAddress-firstBracketAddress-3), firstBracketAddress+3, - realnameString); - } else { - fprintf(output, "%.*s in %.*s\n", - (int)(secondBracketAddress-firstBracketAddress-3), firstBracketAddress+3, - (int)(firstBracketAddress-symbolString), symbolString); - } + + char **symbolStringList = backtrace_symbols(callstackArray, currentdepth); + if (!symbolStringList) { + fputs("Callstack could not be obtained\n", output); + return; + } + + fputs("Callstack:\n", output); + bool own_code = false; + char demangle_buffer[2048]= {0}; + for (int i = offset; i < maxdepth; ++i) { + const char * const symbolString = symbolStringList[i]; + // skip all leading libc symbols so the first symbol is our code + if (omit_above_own && !own_code) { + if (strstr(symbolString, "/libc.so.6") != nullptr) + continue; + own_code = true; + offset = i; // make sure the numbering is continous if we omit frames + } + char * realnameString = nullptr; + const char * const firstBracketName = strchr(symbolString, '('); + const char * const firstBracketAddress = strchr(symbolString, '['); + const char * const secondBracketAddress = strchr(firstBracketAddress, ']'); + const char * const beginAddress = firstBracketAddress+3; + const int addressLen = int(secondBracketAddress-beginAddress); + const int padLen = int(ADDRESSDISPLAYLENGTH-addressLen); + if (demangling && firstBracketName) { + const char * const plus = strchr(firstBracketName, '+'); + if (plus && (plus>(firstBracketName+1))) { + char input_buffer[1024]= {0}; + strncpy(input_buffer, firstBracketName+1, plus-firstBracketName-1); + size_t length = getArrayLength(demangle_buffer); + int status=0; + // We're violating the specification - passing stack address instead of malloc'ed heap. + // Benefit is that no further heap is required, while there is sufficient stack... + realnameString = abi::__cxa_demangle(input_buffer, demangle_buffer, &length, &status); // non-NULL on success } - // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion) - code matches the documented usage - free(symbolStringList); + } + const int ordinal=i-offset; + fprintf(output, "#%-2d 0x", + ordinal); + if (padLen>0) + fprintf(output, "%0*d", + padLen, 0); + if (realnameString) { + fprintf(output, "%.*s in %s\n", + (int)(secondBracketAddress-firstBracketAddress-3), firstBracketAddress+3, + realnameString); } else { - fputs("Callstack could not be obtained\n", output); + fprintf(output, "%.*s in %.*s\n", + (int)(secondBracketAddress-firstBracketAddress-3), firstBracketAddress+3, + (int)(firstBracketAddress-symbolString), symbolString); } } + // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion) - code matches the documented usage + free(symbolStringList); #undef ADDRESSDISPLAYLENGTH } diff --git a/cli/stacktrace.h b/cli/stacktrace.h index 3922384d738..f72d5c75ea8 100644 --- a/cli/stacktrace.h +++ b/cli/stacktrace.h @@ -30,8 +30,14 @@ * That is very sensitive to the operating system, hardware, compiler and runtime. * The code is not meant for production environment! * One reason is named first: it's using functions not whitelisted for usage in a signal handler function. + * + * @param output the descriptor to write the trace to + * @param start_idx the frame index to start with + * @param demangling controls demangling of symbols + * @param maxdepth the maximum number of frames to list (32 at most or if -1) + * @param omit_above_own omit top frames which are above our own code (i.e. libc symbols) */ -void print_stacktrace(FILE* output, bool demangling, int maxdepth, bool lowMem); +void print_stacktrace(FILE* output, int start_idx, bool demangling, int maxdepth, bool omit_above_own); #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b14648d0105..4ec673ba152 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,6 @@ if (BUILD_TESTS) + add_subdirectory(signal) + file(GLOB hdrs "*.h") file(GLOB srcs "*.cpp") list(APPEND testrunner_SOURCES ${hdrs} ${srcs} $) diff --git a/test/signal/CMakeLists.txt b/test/signal/CMakeLists.txt new file mode 100644 index 00000000000..6bce413a64c --- /dev/null +++ b/test/signal/CMakeLists.txt @@ -0,0 +1,24 @@ +if (CMAKE_VERSION VERSION_EQUAL "3.13" OR CMAKE_VERSION VERSION_GREATER "3.13") + # target_link_options requires CMake 3.13 + + add_executable(test-signalhandler + test-signalhandler.cpp + ${PROJECT_SOURCE_DIR}/cli/signalhandler.cpp + ${PROJECT_SOURCE_DIR}/cli/stacktrace.cpp) + target_include_directories(test-signalhandler PRIVATE ${PROJECT_SOURCE_DIR}/cli ${PROJECT_SOURCE_DIR}/lib) + # names for static functions are omitted from trace + target_compile_options_safe(test-signalhandler -Wno-missing-declarations) + target_compile_options_safe(test-signalhandler -Wno-missing-prototypes) + # required for backtrace() to produce function names + target_link_options(test-signalhandler PRIVATE -rdynamic) + + add_executable(test-stacktrace + test-stacktrace.cpp + ${PROJECT_SOURCE_DIR}/cli/stacktrace.cpp) + target_include_directories(test-stacktrace PRIVATE ${PROJECT_SOURCE_DIR}/cli ${PROJECT_SOURCE_DIR}/lib) + # names for static functions are omitted from trace + target_compile_options_safe(test-stacktrace -Wno-missing-declarations) + target_compile_options_safe(test-stacktrace -Wno-missing-prototypes) + # required for backtrace() to produce function names + target_link_options(test-stacktrace PRIVATE -rdynamic) +endif() \ No newline at end of file diff --git a/test/signal/test-signalhandler.cpp b/test/signal/test-signalhandler.cpp new file mode 100644 index 00000000000..4be872564d1 --- /dev/null +++ b/test/signal/test-signalhandler.cpp @@ -0,0 +1,77 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2024 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 "config.h" + +#if defined(USE_UNIX_SIGNAL_HANDLING) +#include "signalhandler.h" + +#include +#include +#include + +// static functions are omitted from trace + +/*static*/ NORETURN void my_assert() +{ + assert(false); +} + +/*static*/ NORETURN void my_abort() +{ + abort(); +} + +/*static*/ void my_segv() +{ + // cppcheck-suppress nullPointer + ++*(int*)nullptr; +} + +/*static*/ void my_fpe() +{ +#if !defined(__APPLE__) + feenableexcept(FE_ALL_EXCEPT); // TODO: check result +#endif + std::feraiseexcept(FE_UNDERFLOW | FE_DIVBYZERO); // TODO: check result + // TODO: to generate this via code +} +#endif + +int main(int argc, const char * const argv[]) +{ +#if defined(USE_UNIX_SIGNAL_HANDLING) + if (argc != 2) + return 1; + + register_signal_handler(); + + if (strcmp(argv[1], "assert") == 0) + my_assert(); + else if (strcmp(argv[1], "abort") == 0) + my_abort(); + else if (strcmp(argv[1], "fpe") == 0) + my_fpe(); + else if (strcmp(argv[1], "segv") == 0) + my_segv(); + + return 0; +#else + return 1; +#endif +} diff --git a/test/signal/test-signalhandler.py b/test/signal/test-signalhandler.py new file mode 100644 index 00000000000..93303d00859 --- /dev/null +++ b/test/signal/test-signalhandler.py @@ -0,0 +1,84 @@ +import subprocess +import os +import sys +import pytest + +def _lookup_cppcheck_exe(exe_name): + # path the script is located in + script_path = os.path.dirname(os.path.realpath(__file__)) + + if sys.platform == "win32": + exe_name += ".exe" + + for base in (script_path + '/../../', './'): + for path in ('', 'bin/', 'bin/debug/'): + exe_path = base + path + exe_name + if os.path.isfile(exe_path): + print("using '{}'".format(exe_path)) + return exe_path + + return None + +def _call_process(arg): + exe = _lookup_cppcheck_exe('test-signalhandler') + if exe is None: + raise Exception('executable not found') + p = subprocess.Popen([exe, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + comm = p.communicate() + stdout = comm[0].decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + stderr = comm[1].decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + return p.returncode, stdout, stderr + + +def test_assert(): + exitcode, stdout, stderr = _call_process('assert') + if sys.platform == "darwin": + assert stderr.startswith("Assertion failed: (false), function my_assert, file test-signalhandler.cpp, line "), stderr + else: + assert stderr.endswith("test-signalhandler.cpp:32: void my_assert(): Assertion `false' failed.\n"), stderr + lines = stdout.splitlines() + assert lines[0] == 'Internal error: cppcheck received signal SIGABRT - abort or assertion' + # no stacktrace of MacOs + if sys.platform != "darwin": + assert lines[1] == 'Callstack:' + assert lines[2].endswith('my_abort()'), lines[2] # TODO: wrong function + assert lines[len(lines)-1] == 'Please report this to the cppcheck developers!' + + +def test_abort(): + exitcode, stdout, stderr = _call_process('abort') + lines = stdout.splitlines() + assert lines[0] == 'Internal error: cppcheck received signal SIGABRT - abort or assertion' + # no stacktrace on MaCos + if sys.platform != "darwin": + assert lines[1] == 'Callstack:' + assert lines[2].endswith('my_segv()'), lines[2] # TODO: wrong function + assert lines[len(lines)-1] == 'Please report this to the cppcheck developers!' + + +def test_segv(): + exitcode, stdout, stderr = _call_process('segv') + assert stderr == '' + lines = stdout.splitlines() + if sys.platform == "darwin": + assert lines[0] == 'Internal error: cppcheck received signal SIGSEGV - SEGV_MAPERR (at 0x0).' + else: + assert lines[0] == 'Internal error: cppcheck received signal SIGSEGV - SEGV_MAPERR (reading at 0x0).' + # no stacktrace on MacOS + if sys.platform != "darwin": + assert lines[1] == 'Callstack:' + assert lines[2].endswith('my_segv()'), lines[2] # TODO: wrong function + assert lines[len(lines)-1] == 'Please report this to the cppcheck developers!' + + +# TODO: make this work +@pytest.mark.skip +def test_fpe(): + exitcode, stdout, stderr = _call_process('fpe') + assert stderr == '' + lines = stdout.splitlines() + assert lines[0].startswith('Internal error: cppcheck received signal SIGFPE - FPE_FLTDIV (at 0x7f'), lines[0] + assert lines[0].endswith(').'), lines[0] + assert lines[1] == 'Callstack:' + assert lines[2].endswith('my_fpe()'), lines[2] + assert lines[len(lines)-1] == 'Please report this to the cppcheck developers!' diff --git a/test/signal/test-stacktrace.cpp b/test/signal/test-stacktrace.cpp new file mode 100644 index 00000000000..8cace95fb9e --- /dev/null +++ b/test/signal/test-stacktrace.cpp @@ -0,0 +1,46 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2024 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 "config.h" + +#ifdef USE_UNIX_BACKTRACE_SUPPORT +#include "stacktrace.h" + +// static functions are omitted from trace + +/*static*/ void my_func_2() +{ + print_stacktrace(stdout, 0, true, -1, true); +} + +/*static*/ void my_func() +{ + my_func_2(); +} +#endif + +int main() +{ +#ifdef USE_UNIX_BACKTRACE_SUPPORT + my_func(); + + return 0; +#else + return 1; +#endif +} diff --git a/test/signal/test-stacktrace.py b/test/signal/test-stacktrace.py new file mode 100644 index 00000000000..8f9e5763207 --- /dev/null +++ b/test/signal/test-stacktrace.py @@ -0,0 +1,38 @@ +import subprocess +import os +import sys + +def _lookup_cppcheck_exe(exe_name): + # path the script is located in + script_path = os.path.dirname(os.path.realpath(__file__)) + + if sys.platform == "win32": + exe_name += ".exe" + + for base in (script_path + '/../../', './'): + for path in ('', 'bin/', 'bin/debug/'): + exe_path = base + path + exe_name + if os.path.isfile(exe_path): + print("using '{}'".format(exe_path)) + return exe_path + + return None + +def _call_process(): + exe = _lookup_cppcheck_exe('test-stacktrace') + if exe is None: + raise Exception('executable not found') + p = subprocess.Popen([exe], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + comm = p.communicate() + stdout = comm[0].decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + stderr = comm[1].decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + return p.returncode, stdout, stderr + + +def test_stack(): + exitcode, stdout, stderr = _call_process() + assert stderr == '' + lines = stdout.splitlines() + assert lines[0] == 'Callstack:' + assert lines[1].endswith('my_func_2()') + assert lines[2].endswith('my_func()') diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index ffc2dae4775..06cbbd96667 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -27,10 +27,10 @@ - + @@ -114,10 +114,10 @@ - + diff --git a/test/testrunner.vcxproj.filters b/test/testrunner.vcxproj.filters index 7aa2676a830..5728c84b3ad 100644 --- a/test/testrunner.vcxproj.filters +++ b/test/testrunner.vcxproj.filters @@ -223,7 +223,7 @@ Source Files - + Source Files @@ -285,7 +285,7 @@ Header Files - + Header Files diff --git a/tools/dmake/dmake.cpp b/tools/dmake/dmake.cpp index e34432afcc6..eefc2a3d377 100644 --- a/tools/dmake/dmake.cpp +++ b/tools/dmake/dmake.cpp @@ -662,7 +662,7 @@ int main(int argc, char **argv) fout << "\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LIBS) $(LDFLAGS) $(RDYNAMIC)\n\n"; fout << "all:\tcppcheck testrunner\n\n"; // TODO: generate from clifiles - fout << "testrunner: $(EXTOBJ) $(TESTOBJ) $(LIBOBJ) cli/executor.o cli/processexecutor.o cli/singleexecutor.o cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/cppcheckexecutorseh.o cli/cppcheckexecutorsig.o cli/stacktrace.o cli/filelister.o\n"; + fout << "testrunner: $(EXTOBJ) $(TESTOBJ) $(LIBOBJ) cli/executor.o cli/processexecutor.o cli/singleexecutor.o cli/threadexecutor.o cli/cmdlineparser.o cli/cppcheckexecutor.o cli/cppcheckexecutorseh.o cli/signalhandler.o cli/stacktrace.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";