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";