/ externals / catch / src / catch2 / internal / catch_fatal_condition_handler.cpp
catch_fatal_condition_handler.cpp
  1  
  2  //              Copyright Catch2 Authors
  3  // Distributed under the Boost Software License, Version 1.0.
  4  //   (See accompanying file LICENSE.txt or copy at
  5  //        https://www.boost.org/LICENSE_1_0.txt)
  6  
  7  // SPDX-License-Identifier: BSL-1.0
  8  
  9  /** \file
 10   * This file provides platform specific implementations of FatalConditionHandler
 11   *
 12   * This means that there is a lot of conditional compilation, and platform
 13   * specific code. Currently, Catch2 supports a dummy handler (if no
 14   * handler is desired), and 2 platform specific handlers:
 15   *  * Windows' SEH
 16   *  * POSIX signals
 17   *
 18   * Consequently, various pieces of code below are compiled if either of
 19   * the platform specific handlers is enabled, or if none of them are
 20   * enabled. It is assumed that both cannot be enabled at the same time,
 21   * and doing so should cause a compilation error.
 22   *
 23   * If another platform specific handler is added, the compile guards
 24   * below will need to be updated taking these assumptions into account.
 25   */
 26  
 27  #include <catch2/internal/catch_fatal_condition_handler.hpp>
 28  
 29  #include <catch2/internal/catch_context.hpp>
 30  #include <catch2/internal/catch_enforce.hpp>
 31  #include <catch2/interfaces/catch_interfaces_capture.hpp>
 32  #include <catch2/internal/catch_windows_h_proxy.hpp>
 33  #include <catch2/internal/catch_stdstreams.hpp>
 34  
 35  #include <algorithm>
 36  
 37  #if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )
 38  
 39  namespace Catch {
 40  
 41      // If neither SEH nor signal handling is required, the handler impls
 42      // do not have to do anything, and can be empty.
 43      void FatalConditionHandler::engage_platform() {}
 44      void FatalConditionHandler::disengage_platform() noexcept {}
 45      FatalConditionHandler::FatalConditionHandler() = default;
 46      FatalConditionHandler::~FatalConditionHandler() = default;
 47  
 48  } // end namespace Catch
 49  
 50  #endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS
 51  
 52  #if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )
 53  #error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time"
 54  #endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS
 55  
 56  #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
 57  
 58  namespace {
 59      //! Signals fatal error message to the run context
 60      void reportFatal( char const * const message ) {
 61          Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
 62      }
 63  
 64      //! Minimal size Catch2 needs for its own fatal error handling.
 65      //! Picked empirically, so it might not be sufficient on all
 66      //! platforms, and for all configurations.
 67      constexpr std::size_t minStackSizeForErrors = 32 * 1024;
 68  } // end unnamed namespace
 69  
 70  #endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS
 71  
 72  #if defined( CATCH_CONFIG_WINDOWS_SEH )
 73  
 74  namespace Catch {
 75  
 76      struct SignalDefs { DWORD id; const char* name; };
 77  
 78      // There is no 1-1 mapping between signals and windows exceptions.
 79      // Windows can easily distinguish between SO and SigSegV,
 80      // but SigInt, SigTerm, etc are handled differently.
 81      static SignalDefs signalDefs[] = {
 82          { EXCEPTION_ILLEGAL_INSTRUCTION,  "SIGILL - Illegal instruction signal" },
 83          { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" },
 84          { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" },
 85          { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" },
 86      };
 87  
 88      static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) {
 89          for (auto const& def : signalDefs) {
 90              if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
 91                  reportFatal(def.name);
 92              }
 93          }
 94          // If its not an exception we care about, pass it along.
 95          // This stops us from eating debugger breaks etc.
 96          return EXCEPTION_CONTINUE_SEARCH;
 97      }
 98  
 99      // Since we do not support multiple instantiations, we put these
100      // into global variables and rely on cleaning them up in outlined
101      // constructors/destructors
102      static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr;
103  
104  
105      // For MSVC, we reserve part of the stack memory for handling
106      // memory overflow structured exception.
107      FatalConditionHandler::FatalConditionHandler() {
108          ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);
109          if (!SetThreadStackGuarantee(&guaranteeSize)) {
110              // We do not want to fully error out, because needing
111              // the stack reserve should be rare enough anyway.
112              Catch::cerr()
113                  << "Failed to reserve piece of stack."
114                  << " Stack overflows will not be reported successfully.";
115          }
116      }
117  
118      // We do not attempt to unset the stack guarantee, because
119      // Windows does not support lowering the stack size guarantee.
120      FatalConditionHandler::~FatalConditionHandler() = default;
121  
122  
123      void FatalConditionHandler::engage_platform() {
124          // Register as a the top level exception filter.
125          previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter);
126      }
127  
128      void FatalConditionHandler::disengage_platform() noexcept {
129          if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) {
130              Catch::cerr()
131                  << "Unexpected SEH unhandled exception filter on disengage."
132                  << " The filter was restored, but might be rolled back unexpectedly.";
133          }
134          previousTopLevelExceptionFilter = nullptr;
135      }
136  
137  } // end namespace Catch
138  
139  #endif // CATCH_CONFIG_WINDOWS_SEH
140  
141  #if defined( CATCH_CONFIG_POSIX_SIGNALS )
142  
143  #include <signal.h>
144  
145  namespace Catch {
146  
147      struct SignalDefs {
148          int id;
149          const char* name;
150      };
151  
152      static SignalDefs signalDefs[] = {
153          { SIGINT,  "SIGINT - Terminal interrupt signal" },
154          { SIGILL,  "SIGILL - Illegal instruction signal" },
155          { SIGFPE,  "SIGFPE - Floating point error signal" },
156          { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
157          { SIGTERM, "SIGTERM - Termination request signal" },
158          { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
159      };
160  
161  // Older GCCs trigger -Wmissing-field-initializers for T foo = {}
162  // which is zero initialization, but not explicit. We want to avoid
163  // that.
164  #if defined(__GNUC__)
165  #    pragma GCC diagnostic push
166  #    pragma GCC diagnostic ignored "-Wmissing-field-initializers"
167  #endif
168  
169      static char* altStackMem = nullptr;
170      static std::size_t altStackSize = 0;
171      static stack_t oldSigStack{};
172      static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};
173  
174      static void restorePreviousSignalHandlers() noexcept {
175          // We set signal handlers back to the previous ones. Hopefully
176          // nobody overwrote them in the meantime, and doesn't expect
177          // their signal handlers to live past ours given that they
178          // installed them after ours..
179          for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
180              sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
181          }
182          // Return the old stack
183          sigaltstack(&oldSigStack, nullptr);
184      }
185  
186      static void handleSignal( int sig ) {
187          char const * name = "<unknown signal>";
188          for (auto const& def : signalDefs) {
189              if (sig == def.id) {
190                  name = def.name;
191                  break;
192              }
193          }
194          // We need to restore previous signal handlers and let them do
195          // their thing, so that the users can have the debugger break
196          // when a signal is raised, and so on.
197          restorePreviousSignalHandlers();
198          reportFatal( name );
199          raise( sig );
200      }
201  
202      FatalConditionHandler::FatalConditionHandler() {
203          assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists");
204          if (altStackSize == 0) {
205              altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);
206          }
207          altStackMem = new char[altStackSize]();
208      }
209  
210      FatalConditionHandler::~FatalConditionHandler() {
211          delete[] altStackMem;
212          // We signal that another instance can be constructed by zeroing
213          // out the pointer.
214          altStackMem = nullptr;
215      }
216  
217      void FatalConditionHandler::engage_platform() {
218          stack_t sigStack;
219          sigStack.ss_sp = altStackMem;
220          sigStack.ss_size = altStackSize;
221          sigStack.ss_flags = 0;
222          sigaltstack(&sigStack, &oldSigStack);
223          struct sigaction sa = { };
224  
225          sa.sa_handler = handleSignal;
226          sa.sa_flags = SA_ONSTACK;
227          for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
228              sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
229          }
230      }
231  
232  #if defined(__GNUC__)
233  #    pragma GCC diagnostic pop
234  #endif
235  
236  
237      void FatalConditionHandler::disengage_platform() noexcept {
238          restorePreviousSignalHandlers();
239      }
240  
241  } // end namespace Catch
242  
243  #endif // CATCH_CONFIG_POSIX_SIGNALS