From afb8df941e9809e0f69f47114f0a28bff308054e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Tue, 17 Jan 2023 22:59:15 +0100 Subject: [PATCH 1/5] This fixes the detection of test-functions (makes it more robust). The strides are in general not constant between each ctest struct stored in memory, so we need to search specifically for the magic number. With TinyC https://repo.or.cz/tinycc.git on linux, the stride is mostly 14 ints between each entry, but sometimes 16 and 18. On other platforms it is always 14 ints. It now searches 8 ints beyond the ctest struct size, which is double of extra stride sometimes observed with TinyC. Both the current and this search is UB as it accesses memory outside the storage, but works well in practice on most platforms. --- ctest.h | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/ctest.h b/ctest.h index 50f5756..fca32ab 100644 --- a/ctest.h +++ b/ctest.h @@ -41,6 +41,8 @@ union ctest_run_func_union { }; #define CTEST_IMPL_PRAGMA(x) _Pragma (#x) +#define CTEST_CONTAINER_OF(p, T, m) \ + ((T*)((char*)(p) + 0*sizeof((p) == &((T*)0)->m) - offsetof(T, m))) #if defined(__GNUC__) #if defined(__clang__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) @@ -528,28 +530,29 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[] #endif clock_t t1 = clock(); - struct ctest* ctest_begin = &CTEST_IMPL_TNAME(suite, test); - struct ctest* ctest_end = &CTEST_IMPL_TNAME(suite, test); - // find begin and end of section by comparing magics - while (1) { - struct ctest* t = ctest_begin-1; - if (t->magic != CTEST_IMPL_MAGIC) break; - ctest_begin--; - } - while (1) { - struct ctest* t = ctest_end+1; - if (t->magic != CTEST_IMPL_MAGIC) break; - ctest_end++; - } - ctest_end++; // end after last one + unsigned* magic_begin = &CTEST_IMPL_TNAME(suite, test).magic; + unsigned *magic_end = magic_begin, *m; + size_t smax = 8 + sizeof(struct ctest)/sizeof *m; + + for (m = magic_begin - 1; magic_begin - m < smax; --m) + if (*m == CTEST_IMPL_MAGIC) + magic_begin = m; + + for (m = magic_end + 1; m - magic_end < smax; ++m) + if (*m == CTEST_IMPL_MAGIC) + magic_end = m; static struct ctest* test; - for (test = ctest_begin; test != ctest_end; test++) { + for (m = magic_begin; m <= magic_end; ++m) { + while (*m != CTEST_IMPL_MAGIC) ++m; + test = CTEST_CONTAINER_OF(m, struct ctest, magic); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; if (filter(test)) total++; } - for (test = ctest_begin; test != ctest_end; test++) { + for (m = magic_begin; m <= magic_end; ++m) { + while (*m != CTEST_IMPL_MAGIC) ++m; + test = CTEST_CONTAINER_OF(m, struct ctest, magic); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; if (filter(test)) { ctest_errorbuffer[0] = 0; From ba90546699b3e3b72575d05fbb73c37f6183ef81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Tue, 17 Jan 2023 23:32:28 +0100 Subject: [PATCH 2/5] Remove spaces. --- ctest.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ctest.h b/ctest.h index fca32ab..58cb770 100644 --- a/ctest.h +++ b/ctest.h @@ -543,7 +543,7 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[] magic_end = m; static struct ctest* test; - for (m = magic_begin; m <= magic_end; ++m) { + for (m = magic_begin; m <= magic_end; ++m) { while (*m != CTEST_IMPL_MAGIC) ++m; test = CTEST_CONTAINER_OF(m, struct ctest, magic); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; From 3ed78b07fce2b63689031aa2e36a7edc09070b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Wed, 18 Jan 2023 08:01:55 +0100 Subject: [PATCH 3/5] Improve search and minimize access to outside memory bounds. --- ctest.h | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/ctest.h b/ctest.h index 58cb770..f0d4f58 100644 --- a/ctest.h +++ b/ctest.h @@ -73,9 +73,8 @@ struct ctest { ctest_setup_func* setup; ctest_teardown_func* teardown; - int skip; - - unsigned int magic; + int32_t skip; + uint32_t magic; }; #define CTEST_IMPL_NAME(name) ctest_##name @@ -530,27 +529,33 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[] #endif clock_t t1 = clock(); - unsigned* magic_begin = &CTEST_IMPL_TNAME(suite, test).magic; - unsigned *magic_end = magic_begin, *m; - size_t smax = 8 + sizeof(struct ctest)/sizeof *m; + uint32_t* magic_begin = &CTEST_IMPL_TNAME(suite, test).magic; + uint32_t *magic_end = magic_begin, *m; + size_t iskip = sizeof(struct ctest)/sizeof *m; - for (m = magic_begin - 1; magic_begin - m < smax; --m) - if (*m == CTEST_IMPL_MAGIC) + for (m = magic_begin; magic_begin - m <= iskip + 8; --m) { + if (*m == CTEST_IMPL_MAGIC) { magic_begin = m; - - for (m = magic_end + 1; m - magic_end < smax; ++m) - if (*m == CTEST_IMPL_MAGIC) - magic_end = m; - + m -= iskip - 1; + } + } + /* Better not search as it does not appear needed, and minimize accessing illegal memory. + if (magic_begin == magic_end) for (m = magic_end; m - magic_end <= iskip + 8; ++m) { + if (*m == CTEST_IMPL_MAGIC) { + magic_begin = m; + m += iskip - 1; + } + } + */ static struct ctest* test; - for (m = magic_begin; m <= magic_end; ++m) { + for (m = magic_begin; m <= magic_end; m += iskip) { while (*m != CTEST_IMPL_MAGIC) ++m; test = CTEST_CONTAINER_OF(m, struct ctest, magic); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; if (filter(test)) total++; } - for (m = magic_begin; m <= magic_end; ++m) { + for (m = magic_begin; m <= magic_end; m += iskip) { while (*m != CTEST_IMPL_MAGIC) ++m; test = CTEST_CONTAINER_OF(m, struct ctest, magic); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; From 466e8a034a8701464cf0f972fefab128cffb36a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Wed, 18 Jan 2023 08:44:38 +0100 Subject: [PATCH 4/5] Reverted to search both before and after the "reference" test to allow placing main.c before, after or between test files. --- ctest.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ctest.h b/ctest.h index f0d4f58..f48b294 100644 --- a/ctest.h +++ b/ctest.h @@ -539,14 +539,13 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[] m -= iskip - 1; } } - /* Better not search as it does not appear needed, and minimize accessing illegal memory. - if (magic_begin == magic_end) for (m = magic_end; m - magic_end <= iskip + 8; ++m) { + for (m = magic_end; m - magic_end <= iskip + 8; ++m) { if (*m == CTEST_IMPL_MAGIC) { - magic_begin = m; + magic_end = m; m += iskip - 1; } } - */ + static struct ctest* test; for (m = magic_begin; m <= magic_end; m += iskip) { while (*m != CTEST_IMPL_MAGIC) ++m; From 203540057933a47679c2db9c94039822411e0294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Fri, 20 Jan 2023 20:03:34 +0100 Subject: [PATCH 5/5] I'll leave a last commit which will access only 4 bytes outside the outer ctest entry bounds - unless compiler is TINYC on LINUX. The current seach accesses the bytes 56-60 beyond the last entry. On TINYC, it will search up to 44 byte outside if there are gaps between ctest struct entry. --- ctest.h | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/ctest.h b/ctest.h index f48b294..22538db 100644 --- a/ctest.h +++ b/ctest.h @@ -65,6 +65,8 @@ union ctest_run_func_union { #endif struct ctest { + uint32_t magic0, padding; + const char* ssname; // suite name const char* ttname; // test name union ctest_run_func_union run; @@ -74,7 +76,7 @@ struct ctest { ctest_teardown_func* teardown; int32_t skip; - uint32_t magic; + uint32_t magic1; }; #define CTEST_IMPL_NAME(name) ctest_##name @@ -89,7 +91,6 @@ struct ctest { #define CTEST_IMPL_TEARDOWN_FPNAME(sname) CTEST_IMPL_NAME(sname##_teardown_ptr) #define CTEST_IMPL_TEARDOWN_TPNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_teardown_ptr) -#define CTEST_IMPL_MAGIC (0xdeadbeef) #ifdef __APPLE__ #define CTEST_IMPL_SECTION __attribute__ ((used, section ("__DATA, .ctest"), aligned(1))) #else @@ -98,6 +99,7 @@ struct ctest { #define CTEST_IMPL_STRUCT(sname, tname, tskip, tdata, tsetup, tteardown) \ static struct ctest CTEST_IMPL_TNAME(sname, tname) CTEST_IMPL_SECTION = { \ + 0xBADFEED0, 0, \ #sname, \ #tname, \ { (ctest_nullary_run_func) CTEST_IMPL_FNAME(sname, tname) }, \ @@ -105,7 +107,7 @@ struct ctest { (ctest_setup_func*) tsetup, \ (ctest_teardown_func*) tteardown, \ tskip, \ - CTEST_IMPL_MAGIC, \ + 0xBADFEED1, \ } #ifdef __cplusplus @@ -529,34 +531,40 @@ __attribute__((no_sanitize_address)) int ctest_main(int argc, const char *argv[] #endif clock_t t1 = clock(); - uint32_t* magic_begin = &CTEST_IMPL_TNAME(suite, test).magic; - uint32_t *magic_end = magic_begin, *m; + uint32_t* magic_begin = &CTEST_IMPL_TNAME(suite, test).magic1; + uint32_t* magic_end = &CTEST_IMPL_TNAME(suite, test).magic0, *m; size_t iskip = sizeof(struct ctest)/sizeof *m; - for (m = magic_begin; magic_begin - m <= iskip + 8; --m) { - if (*m == CTEST_IMPL_MAGIC) { +#if (defined __TINYC__ && defined __unix__) + #define CTEST_PEEK 12 /* search 4*(1+12) bytes outside ctest entry bounds */ +#else + #define CTEST_PEEK 0 /* access only 4 bytes outside outer ctest entry bounds */ +#endif + for (m = magic_begin; magic_begin - m <= iskip + CTEST_PEEK; --m) { + if (*m == 0xBADFEED1) { magic_begin = m; m -= iskip - 1; } } - for (m = magic_end; m - magic_end <= iskip + 8; ++m) { - if (*m == CTEST_IMPL_MAGIC) { + for (m = magic_end; m - magic_end <= iskip + CTEST_PEEK; ++m) { + if (*m == 0xBADFEED0) { magic_end = m; m += iskip - 1; } } + magic_begin = &CTEST_CONTAINER_OF(magic_begin, struct ctest, magic1)->magic0; static struct ctest* test; for (m = magic_begin; m <= magic_end; m += iskip) { - while (*m != CTEST_IMPL_MAGIC) ++m; - test = CTEST_CONTAINER_OF(m, struct ctest, magic); + while (*m != 0xBADFEED0) ++m; + test = CTEST_CONTAINER_OF(m, struct ctest, magic0); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; if (filter(test)) total++; } for (m = magic_begin; m <= magic_end; m += iskip) { - while (*m != CTEST_IMPL_MAGIC) ++m; - test = CTEST_CONTAINER_OF(m, struct ctest, magic); + while (*m != 0xBADFEED0) ++m; + test = CTEST_CONTAINER_OF(m, struct ctest, magic0); if (test == &CTEST_IMPL_TNAME(suite, test)) continue; if (filter(test)) { ctest_errorbuffer[0] = 0;