-
Notifications
You must be signed in to change notification settings - Fork 137
Expand file tree
/
Copy pathtests_cxx.cpp
More file actions
301 lines (277 loc) · 9.12 KB
/
Copy pathtests_cxx.cpp
File metadata and controls
301 lines (277 loc) · 9.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/* CoffeeCatch C++ test suite.
*
* Copyright (c) 2013, Xavier Roche (http://www.httrack.com/)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the conditions in the LICENSE
* file are met.
*
* Companion to tests.c: exercises the COFFEE_CXX_* macros. Kept separate so the
* C suite stays C (no siglongjmp across C++ frames, and CC/clang coverage is
* preserved). Each case runs in its own forked child, as in tests.c.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "coffeecatch.h"
/* COFFEE_TRY requires its enclosing function to be non-inlined. */
#define NOINLINE __attribute__ ((noinline))
/* Fail the current test with context. Only valid outside a TRY/CATCH block. */
#define CHECK(cond) \
do { \
if (!(cond)) { \
fprintf(stderr, " check failed: %s (%s:%d)\n", \
#cond, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
/* Unmapped first-page address; volatile so the store is not folded away. */
static volatile uintptr_t bad_addr = 0x100;
#define CRASH() (*(volatile int *) bad_addr = 1)
/* --- individual cases: return 0 on success, 1 on failure ----------------- */
static NOINLINE int test_cxx_segv(void) {
volatile int caught = 0, sig = 0;
COFFEE_CXX_TRY() {
CRASH();
} COFFEE_CXX_CATCH() {
caught = 1;
sig = coffeecatch_get_signal();
coffeecatch_cancel_pending_alarm();
} COFFEE_CXX_END();
CHECK(!coffeecatch_inside()); /* the sentinel ran the cleanup */
CHECK(caught);
CHECK(sig == SIGSEGV);
return 0;
}
/* A caught signal arms the deadlock alarm; cancel reports success (rc 0) only
* when an alarm was actually pending, so this asserts the alarm state. */
static NOINLINE int test_cxx_alarm_armed_on_signal(void) {
volatile int caught = 0, rc = -1;
COFFEE_CXX_TRY() {
CRASH();
} COFFEE_CXX_CATCH() {
caught = 1;
rc = coffeecatch_cancel_pending_alarm();
} COFFEE_CXX_END();
CHECK(!coffeecatch_inside());
CHECK(caught);
CHECK(rc == 0);
return 0;
}
/* A C++ throw inside the protected block skips COFFEE_CXX_CATCH() and unwinds
* to the enclosing C++ catch; the sentinel still runs the cleanup on the way. */
static NOINLINE int test_cxx_throw_inside_try(void) {
volatile int coffee_caught = 0, reached = 0, cxx_caught = 0;
try {
COFFEE_CXX_TRY() {
reached = 1;
throw 1;
} COFFEE_CXX_CATCH() {
coffee_caught = 1;
} COFFEE_CXX_END();
} catch (int c) {
cxx_caught = c;
}
CHECK(!coffeecatch_inside()); /* the sentinel cleaned up on the way out */
CHECK(reached);
CHECK(!coffee_caught);
CHECK(cxx_caught);
return 0;
}
/* Re-raising a signal as a C++ exception from COFFEE_CXX_CATCH() reaches the
* enclosing catch. */
static NOINLINE int test_cxx_throw_inside_catch(void) {
volatile int coffee_caught = 0, cxx_caught = 0, rc = -1;
try {
COFFEE_CXX_TRY() {
coffeecatch_abort("deliberate", __FILE__, __LINE__);
} COFFEE_CXX_CATCH() {
coffee_caught = 1;
int sig = coffeecatch_get_signal();
throw sig;
} COFFEE_CXX_END();
} catch (int c) {
cxx_caught = c;
/* The recipe README.md documents: cancel here, where reaching the outer
catch proves the allocator the re-raise used was usable. The sentinel
already ran the cleanup, so this must not depend on per-thread state. */
rc = coffeecatch_cancel_pending_alarm();
}
CHECK(!coffeecatch_inside());
CHECK(coffee_caught);
CHECK(cxx_caught == SIGABRT);
CHECK(rc == 0);
CHECK(alarm(0) == 0);
return 0;
}
/* Return straight out of the protected block. The plain COFFEE_TRY() forbids
* this (cleanup would be skipped); the sentinel makes it safe. */
static NOINLINE void returns_from_cxx_try(volatile int *const reached) {
COFFEE_CXX_TRY() {
*reached = 1;
return;
} COFFEE_CXX_CATCH() {
/* not reached: no signal */
} COFFEE_CXX_END();
}
static NOINLINE int test_cxx_return_from_try(void) {
volatile int reached = 0;
returns_from_cxx_try(&reached);
CHECK(reached);
CHECK(!coffeecatch_inside()); /* the sentinel ran cleanup despite the early return */
return 0;
}
/* Nested COFFEE_CXX_TRY must compile (the sentinel names differ, so an embedder
* building with -Wshadow can nest them) and balance the cleanup on the no-crash
* path. The crashing-nested and mixed C/C++ cases follow. */
static NOINLINE int test_cxx_nested(void) {
volatile int outer_reached = 0, inner_reached = 0;
volatile int outer_caught = 0, inner_caught = 0;
COFFEE_CXX_TRY() {
outer_reached = 1;
COFFEE_CXX_TRY() {
inner_reached = 1;
} COFFEE_CXX_CATCH() {
inner_caught = 1;
} COFFEE_CXX_END();
} COFFEE_CXX_CATCH() {
outer_caught = 1;
} COFFEE_CXX_END();
CHECK(!coffeecatch_inside()); /* both sentinels ran; reenter is back to 0 */
CHECK(outer_reached);
CHECK(inner_reached);
CHECK(!inner_caught);
CHECK(!outer_caught);
return 0;
}
/* A crash in a nested block collapses to the OUTERMOST handler whatever macro
* family each level uses: the inner catch is skipped and the reentry depth must
* still return to zero. That rebalance relies on the reset in
* coffeecatch_try_jump_userland; without it these leave the thread wedged
* inside. The three cases cover C++/C++, C-outer/C++-inner, and C++-outer/
* C-inner nesting. */
static NOINLINE int test_cxx_nested_inner_crash(void) {
volatile int outer_caught = 0, inner_caught = 0;
COFFEE_CXX_TRY() {
COFFEE_CXX_TRY() {
CRASH();
} COFFEE_CXX_CATCH() {
inner_caught = 1;
} COFFEE_CXX_END();
} COFFEE_CXX_CATCH() {
outer_caught = 1;
coffeecatch_cancel_pending_alarm();
} COFFEE_CXX_END();
CHECK(!coffeecatch_inside());
CHECK(outer_caught);
CHECK(!inner_caught);
return 0;
}
static NOINLINE int test_mixed_c_outer_cxx_inner(void) {
volatile int outer_caught = 0, inner_caught = 0;
COFFEE_TRY() {
COFFEE_CXX_TRY() {
CRASH();
} COFFEE_CXX_CATCH() {
inner_caught = 1;
} COFFEE_CXX_END();
} COFFEE_CATCH() {
outer_caught = 1;
coffeecatch_cancel_pending_alarm();
} COFFEE_END();
CHECK(!coffeecatch_inside());
CHECK(outer_caught);
CHECK(!inner_caught);
return 0;
}
static NOINLINE int test_mixed_cxx_outer_c_inner(void) {
volatile int outer_caught = 0, inner_caught = 0;
COFFEE_CXX_TRY() {
COFFEE_TRY() {
CRASH();
} COFFEE_CATCH() {
inner_caught = 1;
} COFFEE_END();
} COFFEE_CXX_CATCH() {
outer_caught = 1;
coffeecatch_cancel_pending_alarm();
} COFFEE_CXX_END();
CHECK(!coffeecatch_inside());
CHECK(outer_caught);
CHECK(!inner_caught);
return 0;
}
/* --- fork harness -------------------------------------------------------- */
struct test {
const char *name;
int (*fn)(void);
const char *skip; /* non-NULL: reason the case is not run */
};
static const struct test tests[] = {
{ "C++: segv (null deref)", test_cxx_segv, NULL },
{ "C++: alarm armed on signal", test_cxx_alarm_armed_on_signal, NULL },
{ "C++: throw inside try", test_cxx_throw_inside_try, NULL },
{ "C++: throw inside catch", test_cxx_throw_inside_catch, NULL },
{ "C++: return from try cleans up", test_cxx_return_from_try, NULL },
{ "C++: nested try", test_cxx_nested, NULL },
{ "C++: nested inner crash", test_cxx_nested_inner_crash, NULL },
{ "mixed: C outer / C++ inner", test_mixed_c_outer_cxx_inner, NULL },
{ "mixed: C++ outer / C inner", test_mixed_cxx_outer_c_inner, NULL },
};
static int run_forked(const struct test *t) {
pid_t pid;
int status = 0;
if (t->skip != NULL) {
printf(" SKIP %s (%s)\n", t->name, t->skip);
return 0;
}
fflush(stdout);
fflush(stderr);
pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
if (pid == 0) {
const int rc = t->fn();
fflush(NULL);
_exit(rc == 0 ? 0 : 1);
}
if (waitpid(pid, &status, 0) != pid) {
perror("waitpid");
return 1;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
printf(" PASS %s\n", t->name);
return 0;
}
if (WIFSIGNALED(status)) {
printf(" FAIL %s (uncaught signal %d)\n", t->name, WTERMSIG(status));
} else {
printf(" FAIL %s (exit %d)\n", t->name, WEXITSTATUS(status));
}
return 1;
}
int main(int argc, char **argv) {
const size_t n = sizeof(tests) / sizeof(tests[0]);
size_t i;
int failures = 0;
(void) argc;
(void) argv;
printf("running %zu coffeecatch C++ tests...\n", n);
for (i = 0; i < n; i++) {
failures += run_forked(&tests[i]);
}
if (failures == 0) {
printf("all %zu tests passed\n", n);
return EXIT_SUCCESS;
}
printf("%d of %zu tests FAILED\n", failures, n);
return EXIT_FAILURE;
}