Skip to content

Commit 5760677

Browse files
committed
Add additional handling for AddressSanitizer
This commit is a continuation of commit 2230e3e ("ignore sanitizer"), which added the "no_sanitize_address" attribute to ctest_main to prevent ASan from flagging reads while discovering the registered test structs. Clang's ASan implementation adds a redzone around globals, so some additional handling is required. Starting with clang 4.0, the "no_sanitize_address" can also be applied to globals to inhibit creating the globals redzone. However, for earlier versions of clang (3.1 was the first to include ASan), we need to work around the redzone. Thanks to ASAN_UNPOISON_MEMORY_REGION(), this is possible. By unpoisoning the memory region and skipping over any alignment padding, we are able to derive the test list, but the "magic" value is now required to bookend the ctest struct. This commit also attempts to make the compiler platform and version checking logic more readable.
1 parent f12e0d8 commit 5760677

File tree

1 file changed

+92
-25
lines changed

1 file changed

+92
-25
lines changed

ctest.h

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,73 @@
2323
#endif
2424

2525
#include <inttypes.h> /* intmax_t, uintmax_t, PRI* */
26-
#include <stddef.h> /* size_t */
26+
#include <stddef.h> /* size_t, offsetof */
2727

2828
typedef void (*ctest_setup_func)(void*);
2929
typedef void (*ctest_teardown_func)(void*);
3030

3131
#define CTEST_IMPL_PRAGMA(x) _Pragma (#x)
3232

33-
#if defined(__GNUC__)
34-
#if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
33+
#define CTEST_IMPL_COMPILER_MINIMUM_VERSION(exp_major, exp_minor, \
34+
real_major, real_minor) \
35+
(((real_major) > (exp_major)) \
36+
|| ((real_major) == (exp_major) && (real_minor) >= (exp_minor)))
37+
38+
#if defined(__GNUC__) && !defined(__clang__)
39+
# define CTEST_IMPL_GCC_MINIMUM_VERSION(major, minor) \
40+
CTEST_IMPL_COMPILER_MINIMUM_VERSION(major, minor, \
41+
__GNUC__, __GNUC_MINOR__)
42+
#else
43+
# define CTEST_IMPL_GCC_MINIMUM_VERSION(major, minor) 0
44+
#endif
45+
46+
#ifdef __clang__
47+
# define CTEST_IMPL_CLANG_MINIMUM_VERSION(major, minor) \
48+
CTEST_IMPL_COMPILER_MINIMUM_VERSION(major, minor, \
49+
__clang_major__, __clang_minor__)
50+
#else
51+
# define CTEST_IMPL_CLANG_MINIMUM_VERSION(major, minor) 0
52+
#endif
53+
54+
#if CTEST_IMPL_GCC_MINIMUM_VERSION(4, 6) || defined(__clang__)
3555
/* the GCC argument will work for both gcc and clang */
3656
#define CTEST_IMPL_DIAG_PUSH_IGNORED(w) \
3757
CTEST_IMPL_PRAGMA(GCC diagnostic push) \
3858
CTEST_IMPL_PRAGMA(GCC diagnostic ignored "-W" #w)
3959
#define CTEST_IMPL_DIAG_POP() \
4060
CTEST_IMPL_PRAGMA(GCC diagnostic pop)
41-
#else
61+
#elif defined(__GNUC__)
4262
/* the push/pop functionality wasn't in gcc until 4.6, fallback to "ignored" */
4363
#define CTEST_IMPL_DIAG_PUSH_IGNORED(w) \
4464
CTEST_IMPL_PRAGMA(GCC diagnostic ignored "-W" #w)
4565
#define CTEST_IMPL_DIAG_POP()
46-
#endif
4766
#else
4867
/* leave them out entirely for non-GNUC compilers */
4968
#define CTEST_IMPL_DIAG_PUSH_IGNORED(w)
5069
#define CTEST_IMPL_DIAG_POP()
5170
#endif
5271

72+
#if CTEST_IMPL_GCC_MINIMUM_VERSION(4, 8)
73+
# define CTEST_IMPL_NO_ASAN_FUNCTION __attribute__ ((no_sanitize_address))
74+
# define CTEST_IMPL_NO_ASAN_VARIABLE
75+
#elif CTEST_IMPL_CLANG_MINIMUM_VERSION(4, 0)
76+
# define CTEST_IMPL_NO_ASAN_FUNCTION __attribute__ ((no_sanitize_address))
77+
# define CTEST_IMPL_NO_ASAN_VARIABLE __attribute__ ((no_sanitize_address))
78+
#elif CTEST_IMPL_CLANG_MINIMUM_VERSION(3, 1) && __has_feature(address_sanitizer)
79+
# warning "Clang 3.x AddressSanitizer may interfere with ctest, proceed with caution!"
80+
# define CTEST_IMPL_NO_ASAN_FUNCTION __attribute__ ((no_sanitize_address))
81+
# define CTEST_IMPL_NO_ASAN_VARIABLE
82+
# define CTEST_IMPL_CLANG_POISIONED_GLOBALS
83+
#else
84+
# define CTEST_IMPL_NO_ASAN_FUNCTION
85+
# define CTEST_IMPL_NO_ASAN_VARIABLE
86+
#endif
87+
5388
CTEST_IMPL_DIAG_PUSH_IGNORED(strict-prototypes)
5489

5590
struct ctest {
91+
unsigned int magic1; // must be first!
92+
5693
const char* ssname; // suite name
5794
const char* ttname; // test name
5895
void (*run)();
@@ -63,7 +100,7 @@ struct ctest {
63100

64101
int skip;
65102

66-
unsigned int magic;
103+
unsigned int magic2; // must be last!
67104
};
68105

69106
CTEST_IMPL_DIAG_POP()
@@ -86,15 +123,19 @@ CTEST_IMPL_DIAG_POP()
86123
#endif
87124

88125
#define CTEST_IMPL_STRUCT(sname, tname, tskip, tdata, tsetup, tteardown) \
89-
static struct ctest CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_SECTION = { \
126+
CTEST_IMPL_NO_ASAN_VARIABLE \
127+
CTEST_IMPL_SECTION \
128+
static struct ctest CTEST_IMPL_TNAME(sname, tname) = { \
129+
.magic1 = CTEST_IMPL_MAGIC, \
90130
.ssname=#sname, \
91131
.ttname=#tname, \
92132
.run = CTEST_IMPL_FNAME(sname, tname), \
93133
.data = tdata, \
94134
.setup = (ctest_setup_func*) tsetup, \
95135
.teardown = (ctest_teardown_func*) tteardown, \
96136
.skip = tskip, \
97-
.magic = CTEST_IMPL_MAGIC }
137+
.magic2 = CTEST_IMPL_MAGIC, \
138+
}
98139

99140
#define CTEST_SETUP(sname) \
100141
static void CTEST_IMPL_SETUP_FNAME(sname)(struct CTEST_IMPL_DATA_SNAME(sname)* data); \
@@ -188,12 +229,13 @@ void assert_dbl_far(double exp, double real, double tol, const char* caller, int
188229

189230
#include <setjmp.h>
190231
#include <stdarg.h>
232+
#include <stdbool.h>
233+
#include <stdint.h>
191234
#include <stdio.h>
235+
#include <stdlib.h>
192236
#include <string.h>
193237
#include <sys/time.h>
194238
#include <unistd.h>
195-
#include <stdint.h>
196-
#include <stdlib.h>
197239
#include <wchar.h>
198240

199241
static size_t ctest_errorsize;
@@ -449,9 +491,42 @@ static void sighandler(int signum)
449491
}
450492
#endif
451493

452-
int ctest_main(int argc, const char *argv[]);
494+
#ifdef CTEST_IMPL_CLANG_POISIONED_GLOBALS
495+
#include <sanitizer/asan_interface.h>
496+
#endif
497+
498+
static struct ctest* ctest_adjacent_test(const struct ctest* test,
499+
int direction, size_t offset)
500+
{
501+
const volatile unsigned int* magic = (const unsigned int*)
502+
((const char*)
503+
(test + direction) + offset);
504+
505+
#if CTEST_IMPL_POISIONED_GLOBALS
506+
while (ASAN_UNPOISON_MEMORY_REGION(magic, sizeof(*magic)), *magic == 0) {
507+
magic += direction;
508+
}
509+
#endif
510+
511+
CTEST_IMPL_DIAG_PUSH_IGNORED(cast-qual)
512+
return *magic == CTEST_IMPL_MAGIC ? (struct ctest*)
513+
((char*)magic - offset)
514+
: NULL;
515+
CTEST_IMPL_DIAG_POP()
516+
}
453517

454-
__attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[])
518+
static struct ctest* ctest_next_test(const struct ctest* test)
519+
{
520+
return ctest_adjacent_test(test, 1, offsetof(struct ctest, magic1));
521+
}
522+
523+
static struct ctest* ctest_prev_test(const struct ctest* test)
524+
{
525+
return ctest_adjacent_test(test, -1, offsetof(struct ctest, magic2));
526+
}
527+
528+
CTEST_IMPL_NO_ASAN_FUNCTION
529+
int ctest_main(int argc, const char *argv[])
455530
{
456531
static int total = 0;
457532
static int num_ok = 0;
@@ -476,27 +551,19 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[]
476551
uint64_t t1 = getCurrentTime();
477552

478553
struct ctest* ctest_begin = &CTEST_IMPL_TNAME(suite, test);
479-
struct ctest* ctest_end = &CTEST_IMPL_TNAME(suite, test);
480-
// find begin and end of section by comparing magics
481-
while (1) {
482-
struct ctest* t = ctest_begin-1;
483-
if (t->magic != CTEST_IMPL_MAGIC) break;
484-
ctest_begin--;
485-
}
486554
while (1) {
487-
struct ctest* t = ctest_end+1;
488-
if (t->magic != CTEST_IMPL_MAGIC) break;
489-
ctest_end++;
555+
struct ctest* t = ctest_prev_test(ctest_begin);
556+
if (t == NULL) break;
557+
ctest_begin = t;
490558
}
491-
ctest_end++; // end after last one
492559

493560
static struct ctest* test;
494-
for (test = ctest_begin; test != ctest_end; test++) {
561+
for (test = ctest_begin; test != NULL; test = ctest_next_test(test)) {
495562
if (test == &CTEST_IMPL_TNAME(suite, test)) continue;
496563
if (filter(test)) total++;
497564
}
498565

499-
for (test = ctest_begin; test != ctest_end; test++) {
566+
for (test = ctest_begin; test != NULL; test = ctest_next_test(test)) {
500567
if (test == &CTEST_IMPL_TNAME(suite, test)) continue;
501568
if (filter(test)) {
502569
ctest_errorbuffer[0] = 0;

0 commit comments

Comments
 (0)