GCC Code Coverage Report


Directory: include/tts/
File: include/tts/engine/guard.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 27 48 56.2%
Functions: 5 8 62.5%
Branches: 13 31 41.9%

Line Branch Exec Source
1 //======================================================================================================================
2 //! @file
3 /*
4 TTS - Tiny Test System
5 Copyright : TTS Contributors & Maintainers
6 SPDX-License-Identifier: BSL-1.0
7 */
8 //======================================================================================================================
9 #pragma once
10
11 #include <tts/engine/abort.hpp>
12 #include <tts/engine/abort_report.hpp>
13 #include <tts/engine/environment.hpp>
14 #include <tts/engine/test.hpp>
15
16 #if defined(__EMSCRIPTEN__)
17 #elif defined(_WIN32)
18 #define WIN32_LEAN_AND_MEAN
19 #define NOMINMAX
20 #include <windows.h>
21 #include <csignal>
22 #else
23 #include <csignal>
24 #endif
25
26 namespace tts::_
27 {
28 // Read once: a debugger or a sanitizer wants the signal for itself.
29 282 inline bool crash_guard_enabled()
30 {
31
3/4
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 188 times.
✓ Branch 3 taken 94 times.
✗ Branch 4 not taken.
282 static bool that = !::tts::arguments()("--no-crash-guard");
32 282 return that;
33 }
34
35 // Not async-signal-safe: each handler guards its own re-entry before calling this.
36 ✗ inline void report_crash(char const* cause, void const* address) // NOSONAR - an address is void*
37 {
38 ::tts::text line =
39 ✗ address ? ::tts::text {"TEST: '%s' - @@ CRASHED @@ %s at %p", current_test, cause, address}
40 ✗ : ::tts::text {"TEST: '%s' - @@ CRASHED @@ %s", current_test, cause};
41
42 ✗ report_abort(line.data(), "CRASH");
43 ✗ }
44 }
45
46 #if defined(__EMSCRIPTEN__)
47 namespace tts::_
48 {
49 // No process-wide signal delivery here: nothing to arm.
50 struct crash_guard
51 {
52 };
53 }
54 #elif defined(_WIN32)
55 namespace tts::_
56 {
57 inline LONG WINAPI crash_filter(PEXCEPTION_POINTERS info)
58 {
59 auto code = info->ExceptionRecord->ExceptionCode;
60
61 char const* cause = nullptr;
62 switch(code)
63 {
64 case EXCEPTION_ACCESS_VIOLATION: cause = "access violation"; break;
65 case EXCEPTION_STACK_OVERFLOW: cause = "stack overflow"; break;
66 case EXCEPTION_INT_DIVIDE_BY_ZERO: cause = "integer divide by zero"; break;
67 case EXCEPTION_ILLEGAL_INSTRUCTION: cause = "illegal instruction"; break;
68 default: return EXCEPTION_CONTINUE_SEARCH;
69 }
70
71 static bool reporting = false;
72 if(reporting) exit_now();
73 reporting = true;
74
75 report_crash(cause, info->ExceptionRecord->ExceptionAddress);
76 perform_abort(static_cast<int>(code));
77 }
78
79 // The vectored handler never sees a raised signal, so the CRT dispositions cover them too.
80 inline constexpr int crash_signals[] = {// NOSONAR - <array> costs compile time
81 SIGSEGV,
82 SIGFPE,
83 SIGILL,
84 SIGABRT};
85 inline constexpr std::size_t crash_signal_count =
86 sizeof(crash_signals) / sizeof(crash_signals[ 0 ]);
87
88 inline char const* signal_name(int sig)
89 {
90 switch(sig)
91 {
92 case SIGSEGV: return "SIGSEGV";
93 case SIGFPE: return "SIGFPE";
94 case SIGILL: return "SIGILL";
95 case SIGABRT: return "SIGABRT";
96 default: return "signal";
97 }
98 }
99 }
100
101 extern "C"
102 {
103 [[noreturn]] inline void tts_crash_on_signal(int sig)
104 {
105 static bool reporting = false;
106 if(reporting) ::tts::_::exit_now();
107 reporting = true;
108
109 ::tts::_::report_crash(::tts::_::signal_name(sig), nullptr);
110 ::tts::_::perform_abort(sig);
111 }
112 }
113
114 namespace tts::_
115 {
116 // The stack overflow exception needs this reserve to be delivered at all. It belongs to the
117 // thread, not to a case, and Windows keeps it until the thread ends.
118 inline bool stack_reserve_ready()
119 {
120 static bool const that = []
121 {
122 ULONG guarantee = 64u * 1024u;
123 return SetThreadStackGuarantee(&guarantee) != 0;
124 }();
125
126 return that;
127 }
128
129 struct crash_guard
130 {
131 crash_guard()
132 {
133 if(!armed_) return;
134
135 stack_reserve_ready();
136 handle_ = AddVectoredExceptionHandler(1, &crash_filter);
137
138 for(std::size_t i = 0; i < crash_signal_count; ++i)
139 previous_[ i ] = signal(crash_signals[ i ], &tts_crash_on_signal);
140 }
141
142 ~crash_guard()
143 {
144 if(!armed_) return;
145
146 if(handle_) RemoveVectoredExceptionHandler(handle_);
147
148 for(std::size_t i = 0; i < crash_signal_count; ++i)
149 if(previous_[ i ] && previous_[ i ] != SIG_ERR) signal(crash_signals[ i ], previous_[ i ]);
150 }
151
152 crash_guard(crash_guard const&) = delete;
153 crash_guard& operator=(crash_guard const&) = delete;
154
155 PVOID handle_ = nullptr;
156 using handler_t = void (*)(int);
157 handler_t previous_[ crash_signal_count ] = {}; // NOSONAR - <array> costs compile time
158 bool armed_ = crash_guard_enabled();
159 };
160 }
161 #else
162 namespace tts::_
163 {
164 inline constexpr int crash_signals[] = {// NOSONAR - <array> costs compile time
165 SIGSEGV,
166 SIGBUS,
167 SIGFPE,
168 SIGILL,
169 SIGABRT};
170 inline constexpr std::size_t crash_signal_count =
171 sizeof(crash_signals) / sizeof(crash_signals[ 0 ]);
172
173 ✗ inline char const* signal_name(int sig)
174 {
175 ✗ switch(sig)
176 {
177 ✗ case SIGSEGV: return "SIGSEGV";
178 ✗ case SIGBUS: return "SIGBUS";
179 ✗ case SIGFPE: return "SIGFPE";
180 ✗ case SIGILL: return "SIGILL";
181 ✗ case SIGABRT: return "SIGABRT";
182 ✗ default: return "signal";
183 }
184 }
185
186 }
187
188 extern "C"
189 {
190 // sigaction hands a mutable siginfo_t, so this parameter cannot take a const.
191 [[noreturn]] inline void
192 ✗ tts_crash_handler(int sig, siginfo_t* info, void*) // NOSONAR - the kernel picks this signature
193 {
194 static sig_atomic_t volatile reporting = 0;
195 ✗ if(reporting)
196 {
197 ✗ signal(sig, SIG_DFL);
198 ✗ raise(sig);
199 ✗ ::tts::_::exit_now();
200 }
201 ✗ reporting = 1;
202
203 ✗ ::tts::_::report_crash(::tts::_::signal_name(sig), sig == SIGABRT ? nullptr : info->si_addr);
204 ✗ ::tts::_::perform_abort(sig);
205 }
206 }
207
208 namespace tts::_
209 {
210 // A stack overflow leaves no stack for the handler to run on, hence the alternate one. It belongs
211 // to the process, not to a case, and the kernel keeps it until someone hands it another.
212 282 inline bool alternate_stack_ready()
213 {
214 // SIGSTKSZ stopped being a constant in glibc 2.34, so the alternate stack has a fixed size.
215 // A stack the machine can run on starts on a 16 byte boundary, which a char array never grants.
216 alignas(16) static char buffer[ 64u * 1024u ] = {}; // NOSONAR - <array> costs compile time
217
218 94 static bool const that = []
219 {
220 94 stack_t alt = {};
221 94 alt.ss_sp = buffer;
222 94 alt.ss_size = sizeof(buffer);
223 94 alt.ss_flags = 0;
224 94 return sigaltstack(&alt, nullptr) == 0;
225
3/4
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 188 times.
✓ Branch 3 taken 94 times.
✗ Branch 4 not taken.
282 }();
226
227 282 return that;
228 }
229
230 struct crash_guard
231 {
232 282 crash_guard()
233 282 {
234
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 282 times.
282 if(!armed_) return;
235
236 282 struct sigaction action = {};
237 282 action.sa_sigaction = &tts_crash_handler;
238
1/2
✓ Branch 1 taken 282 times.
✗ Branch 2 not taken.
282 action.sa_flags = SA_SIGINFO | (alternate_stack_ready() ? SA_ONSTACK : 0);
239 282 sigemptyset(&action.sa_mask);
240
241
2/2
✓ Branch 0 taken 1410 times.
✓ Branch 1 taken 282 times.
1692 for(std::size_t i = 0; i < crash_signal_count; ++i)
242 1410 sigaction(crash_signals[ i ], &action, &previous_[ i ]);
243 }
244
245 282 ~crash_guard()
246 282 {
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 282 times.
282 if(!armed_) return;
248
249
2/2
✓ Branch 0 taken 1410 times.
✓ Branch 1 taken 282 times.
1692 for(std::size_t i = 0; i < crash_signal_count; ++i)
250 1410 sigaction(crash_signals[ i ], &previous_[ i ], nullptr);
251 282 }
252
253 crash_guard(crash_guard const&) = delete;
254 crash_guard& operator=(crash_guard const&) = delete;
255
256 private:
257 struct sigaction previous_[ crash_signal_count ] = {}; // NOSONAR - <array> costs compile time
258 bool armed_ = crash_guard_enabled();
259 };
260 }
261 #endif
262