diff --git a/src/ds/defines.h b/src/ds/defines.h index 7830433..0503a85 100644 --- a/src/ds/defines.h +++ b/src/ds/defines.h @@ -1,4 +1,5 @@ #pragma once +#include #if defined(_MSC_VER) && !defined(__clang__) // 28 is FAST_FAIL_INVALID_BUFFER_ACCESS. Not using the symbolic constant to @@ -109,27 +110,40 @@ namespace snmalloc #define TOSTRING2(expr) #expr #ifdef NDEBUG -# define SNMALLOC_ASSERT(expr) \ +# define SNMALLOC_ASSERT_MSG(...) \ {} #else -# define SNMALLOC_ASSERT(expr) \ +# define SNMALLOC_ASSERT_MSG(expr, fmt, ...) \ + do \ { \ if (!(expr)) \ { \ - snmalloc::error("assert fail: " #expr " in " __FILE__ \ - " on " TOSTRING(__LINE__)); \ + snmalloc::report_fatal_error( \ + "assert fail: {} in {} on {} " fmt "\n", \ + #expr, \ + __FILE__, \ + TOSTRING(__LINE__), \ + ##__VA_ARGS__); \ } \ - } + } while (0) #endif +#define SNMALLOC_ASSERT(expr) SNMALLOC_ASSERT_MSG(expr, "") -#define SNMALLOC_CHECK(expr) \ +#define SNMALLOC_CHECK_MSG(expr, fmt, ...) \ + do \ { \ if (!(expr)) \ { \ - snmalloc::error("Check fail: " #expr " in " __FILE__ \ - " on " TOSTRING(__LINE__)); \ + snmalloc::report_fatal_error( \ + "Check fail: {} in {} on {} " fmt "\n", \ + #expr, \ + __FILE__, \ + TOSTRING(__LINE__), \ + ##__VA_ARGS__); \ } \ - } + } while (0) + +#define SNMALLOC_CHECK(expr) SNMALLOC_CHECK_MSG(expr, "") #ifndef NDEBUG # define SNMALLOC_ASSUME(x) SNMALLOC_ASSERT(x) @@ -184,6 +198,13 @@ namespace snmalloc #else static constexpr bool CHECK_CLIENT = false; #endif + + /** + * Forward declaration so that this can be called before the pal header is + * included. + */ + template + [[noreturn]] inline void report_fatal_error(Args... args); } // namespace snmalloc #ifdef SNMALLOC_CHECK_CLIENT diff --git a/src/ds/helpers.h b/src/ds/helpers.h index 095bbed..dd4d557 100644 --- a/src/ds/helpers.h +++ b/src/ds/helpers.h @@ -256,7 +256,7 @@ namespace snmalloc * build an on-stack buffer containing the formatted string. */ template - class FatalErrorBuilder + class MessageBuilder { /** * The buffer that is used to store the formatted output. @@ -322,17 +322,51 @@ namespace snmalloc */ void append(void* ptr) { - append(static_cast(reinterpret_cast(ptr))); + append(static_cast(reinterpret_cast(ptr))); + // TODO: CHERI bits. + } + + /** + * Append a signed integer to the buffer, as a decimal string. + */ + void append(long long s) + { + if (s < 0) + { + append_char('-'); + s = 0 - s; + } + std::array buf; + const char digits[] = "0123456789"; + for (long i = long(buf.size() - 1); i >= 0; i--) + { + buf[static_cast(i)] = digits[s % 10]; + s /= 10; + } + bool skipZero = true; + for (auto c : buf) + { + if (skipZero && (c == '0')) + { + continue; + } + skipZero = false; + append_char(c); + } + if (skipZero) + { + append_char('0'); + } } /** * Append a size to the buffer, as a hex string. */ - void append(size_t s) + void append(unsigned long long s) { append_char('0'); append_char('x'); - std::array buf; + std::array buf; const char hexdigits[] = "0123456789abcdef"; // Length of string including null terminator static_assert(sizeof(hexdigits) == 0x11); @@ -357,12 +391,44 @@ namespace snmalloc } } + /** + * Overload to force `long` to be promoted to `long long`. + */ + void append(long x) + { + append(static_cast(x)); + } + + /** + * Overload to force `unsigned long` to be promoted to `unsigned long long`. + */ + void append(unsigned long x) + { + append(static_cast(x)); + } + + /** + * Overload to force `int` to be promoted to `long long`. + */ + void append(int x) + { + append(static_cast(x)); + } + + /** + * Overload to force `unsigned int` to be promoted to `unsigned long long`. + */ + void append(unsigned int x) + { + append(static_cast(x)); + } + public: /** * Constructor. Takes a format string and the arguments to output. */ template - SNMALLOC_FAST_PATH FatalErrorBuilder(const char* fmt, Args... args) + SNMALLOC_FAST_PATH MessageBuilder(const char* fmt, Args... args) { buffer[SafeLength] = 0; size_t arg = 0; @@ -382,6 +448,21 @@ namespace snmalloc append_char('\0'); } + /** + * Constructor for trivial format strings (no arguments). This exists to + * allow `MessageBuilder` to be used with macros without special casing + * the single-argument version. + */ + SNMALLOC_FAST_PATH MessageBuilder(const char* fmt) + { + buffer[SafeLength] = 0; + for (const char* s = fmt; *s != 0; ++s) + { + append_char(*s); + } + append_char('\0'); + } + /** * Return the error buffer. */ diff --git a/src/pal/pal.h b/src/pal/pal.h index c18c165..c4dfd0c 100644 --- a/src/pal/pal.h +++ b/src/pal/pal.h @@ -161,10 +161,10 @@ namespace snmalloc * * These types should be sufficient for allocator-related error messages. */ - template + template [[noreturn]] inline void report_fatal_error(Args... args) { - FatalErrorBuilder msg{std::forward(args)...}; + MessageBuilder msg{std::forward(args)...}; Pal::error(msg.get_message()); } diff --git a/src/test/func/jemalloc/jemalloc.cc b/src/test/func/jemalloc/jemalloc.cc index 83c4377..1d0271e 100644 --- a/src/test/func/jemalloc/jemalloc.cc +++ b/src/test/func/jemalloc/jemalloc.cc @@ -1,5 +1,6 @@ #include #include +#include #include #define SNMALLOC_NAME_MANGLE(a) our_##a @@ -59,10 +60,6 @@ # define ALLOCM_ERR_NOT_MOVED OUR_ALLOCM_ERR_NOT_MOVED #endif -#ifdef _MSC_VER -# define __PRETTY_FUNCTION__ __FUNCSIG__ -#endif - using namespace snmalloc; using namespace snmalloc::bits; @@ -91,37 +88,6 @@ namespace } } - /** - * The name of the function under test. This is set in the START_TEST macro - * and used for error reporting in EXPECT. - */ - const char* function = nullptr; - - /** - * Log that the test started. - */ -#define START_TEST(msg) \ - function = __PRETTY_FUNCTION__; \ - fprintf(stderr, "Starting test: " msg "\n"); - - /** - * An assertion that fires even in debug builds. Uses the value set by - * START_TEST. - */ -#define EXPECT(x, msg, ...) \ - if (!(x)) \ - { \ - fprintf( \ - stderr, \ - "%s:%d in %s: " msg "\n", \ - __FILE__, \ - __LINE__, \ - function, \ - ##__VA_ARGS__); \ - fflush(stderr); \ - abort(); \ - } - /** * The default maximum number of bits of address space to use for tests. * This is clamped on platforms without lazy commit because this much RAM @@ -142,7 +108,7 @@ namespace constexpr size_t low = 5; for (size_t base = low; base < Log2MaxSize; base++) { - fprintf(stderr, "\tTrying 0x%zx-byte allocations\n", one_at_bit(base)); + INFO("\tTrying {}-byte allocations\n", one_at_bit(base)); for (size_t i = 0; i < one_at_bit(low); i++) { for (int align = 1; align < 20; align++) @@ -171,13 +137,13 @@ namespace void* ptr = Mallocx(size, flags); EXPECT( ptr != nullptr, - "Failed to allocate 0x%zx bytes with %d bit alignment", + "Failed to allocate {} bytes with {}-bit alignment", size, align); size_t allocated = Sallocx(ptr, 0); EXPECT( allocated == expected, - "Expected to have allocated 0x%zx bytes, got 0x%zx bytes", + "Expected to have allocated {} bytes, got {} bytes", expected, allocated); Dallocx(ptr, 0); @@ -202,11 +168,11 @@ namespace ptr = static_cast(Rallocx(ptr, size * 2, flags)); EXPECT( ptr != nullptr, - "Failed to reallocate for 0x%zx byte allocation", + "Failed to reallocate for {} byte allocation", size * 2); EXPECT( ptr[size] == 0, - "Memory not zero initialised for 0x%zx byte reallocation from 0x%zx " + "Memory not zero initialised for {} byte reallocation from {} " "byte allocation", size * 2, size); @@ -234,11 +200,9 @@ namespace auto test = [](size_t size, int align) { int flags = MALLOCX_LG_ALIGN(align); void* ptr = Mallocx(size, flags); - EXPECT( - ptr != nullptr, "Failed to allocate for 0x%zx byte allocation", size); + EXPECT(ptr != nullptr, "Failed to allocate for zx byte allocation", size); size_t sz = Xallocx(ptr, size, 1024, flags); - EXPECT( - sz >= size, "xalloc returned 0x%zx, expected at least 0x%zx", sz, size); + EXPECT(sz >= size, "xalloc returned {}, expected at least {}", sz, size); Dallocx(ptr, 0); }; test_sizes_and_alignments(test); @@ -258,7 +222,7 @@ namespace int ret = Nallocm(&expected, size, flags); EXPECT( (ret == ALLOCM_SUCCESS), - "nallocm(%zx, %d) failed with error %d", + "nallocm({}, {}) failed with error {}", size, flags, ret); @@ -267,18 +231,18 @@ namespace ret = Allocm(&ptr, &allocated, size, flags); EXPECT( (ptr != nullptr) && (ret == ALLOCM_SUCCESS), - "Failed to allocate 0x%zx bytes with %d bit alignment", + "Failed to allocate {} bytes with {} bit alignment", size, align); EXPECT( allocated == expected, - "Expected to have allocated 0x%zx bytes, got 0x%zx bytes", + "Expected to have allocated {} bytes, got {} bytes", expected, allocated); ret = Sallocm(ptr, &expected, 0); EXPECT( (ret == ALLOCM_SUCCESS) && (allocated == expected), - "Expected to have allocated 0x%zx bytes, got 0x%zx bytes", + "Expected to have allocated {} bytes, got {} bytes", expected, allocated); @@ -301,14 +265,14 @@ namespace void* orig = ptr; EXPECT( (ptr != nullptr) && (ret == ALLOCM_SUCCESS), - "Failed to allocate 0x%zx bytes with %d bit alignment", + "Failed to allocate {} bytes with {} bit alignment", size, align); ret = Rallocm(&ptr, nullptr, allocated + 1, 12, flags | ALLOCM_NO_MOVE); EXPECT( (ret == ALLOCM_ERR_NOT_MOVED) || (ptr == orig), "Expected rallocm not to be able to move or reallocate, but return was " - "%d\n", + "{}\n", ret); Dallocm(ptr, 0); }); @@ -327,8 +291,8 @@ namespace int ret = Allocm(&ptr, nullptr, std::numeric_limits::max() / 2, 0); EXPECT( (ptr == nullptr) && (ret == OUR_ALLOCM_ERR_OOM), - "Expected massive allocation to fail with out of memory (%d), received " - "allocation %p, return code %d", + "Expected massive allocation to fail with out of memory ({}), received " + "allocation {}, return code {}", OUR_ALLOCM_ERR_OOM, ptr, ret); diff --git a/src/test/func/malloc/malloc.cc b/src/test/func/malloc/malloc.cc index 5426956..1ee9067 100644 --- a/src/test/func/malloc/malloc.cc +++ b/src/test/func/malloc/malloc.cc @@ -1,4 +1,5 @@ #include +#include #include #define SNMALLOC_NAME_MANGLE(a) our_##a @@ -14,26 +15,20 @@ constexpr int SUCCESS = 0; void check_result(size_t size, size_t align, void* p, int err, bool null) { bool failed = false; - if (errno != err && err != SUCCESS) - { - // Note: successful calls are allowed to spuriously set errno - printf("Expected error: %d but got %d\n", err, errno); - abort(); - } - + EXPECT( + (errno == err) || (err == SUCCESS), + "Expected error: {} but got {}", + err, + errno); if (null) { - if (p != nullptr) - { - printf("Expected null, and got non-null return!\n"); - abort(); - } + EXPECT(p == nullptr, "Expected null but got {}", p); return; } if ((p == nullptr) && (size != 0)) { - printf("Unexpected null returned.\n"); + INFO("Unexpected null returned.\n"); failed = true; } const auto alloc_size = our_malloc_usable_size(p); @@ -57,8 +52,7 @@ void check_result(size_t size, size_t align, void* p, int err, bool null) const auto cheri_size = __builtin_cheri_length_get(p); if (cheri_size != alloc_size && (size != 0)) { - printf( - "Cheri size is %zu, but required to be %zu.\n", cheri_size, alloc_size); + INFO("Cheri size is {}, but required to be {}.", cheri_size, alloc_size); failed = true; } if (p != nullptr) @@ -82,16 +76,14 @@ void check_result(size_t size, size_t align, void* p, int err, bool null) #endif if (exact_size && (alloc_size != expected_size) && (size != 0)) { - printf( - "Usable size is %zu, but required to be %zu.\n", - alloc_size, - expected_size); + INFO( + "Usable size is {}, but required to be {}.", alloc_size, expected_size); failed = true; } if ((!exact_size) && (alloc_size < expected_size)) { - printf( - "Usable size is %zu, but required to be at least %zu.\n", + INFO( + "Usable size is {}, but required to be at least {}.", alloc_size, expected_size); failed = true; @@ -100,34 +92,27 @@ void check_result(size_t size, size_t align, void* p, int err, bool null) (static_cast(reinterpret_cast(p) % align) != 0) && (size != 0)) { - printf( - "Address is 0x%zx, but required to be aligned to 0x%zx.\n", - reinterpret_cast(p), - align); + INFO("Address is {}, but required to be aligned to {}.\n", p, align); failed = true; } if ( static_cast( reinterpret_cast(p) % natural_alignment(size)) != 0) { - printf( - "Address is 0x%zx, but should have natural alignment to 0x%zx.\n", - reinterpret_cast(p), + INFO( + "Address is {}, but should have natural alignment to {}.\n", + p, natural_alignment(size)); failed = true; } - if (failed) - { - printf("check_result failed! %p", p); - abort(); - } + EXPECT(!failed, "check_result failed! {}", p); our_free(p); } void test_calloc(size_t nmemb, size_t size, int err, bool null) { - printf("calloc(%zu, %zu) combined size %zu\n", nmemb, size, nmemb * size); + START_TEST("calloc({}, {}) combined size {}\n", nmemb, size, nmemb * size); errno = SUCCESS; void* p = our_calloc(nmemb, size); @@ -135,11 +120,7 @@ void test_calloc(size_t nmemb, size_t size, int err, bool null) { for (size_t i = 0; i < (size * nmemb); i++) { - if (((uint8_t*)p)[i] != 0) - { - printf("non-zero at @%zu\n", i); - abort(); - } + EXPECT(((uint8_t*)p)[i] == 0, "non-zero at {}", i); } } check_result(nmemb * size, 1, p, err, null); @@ -151,7 +132,7 @@ void test_realloc(void* p, size_t size, int err, bool null) if (p != nullptr) old_size = our_malloc_usable_size(p); - printf("realloc(%p(%zu), %zu)\n", p, old_size, size); + START_TEST("realloc({}({}), {})", p, old_size, size); errno = SUCCESS; auto new_p = our_realloc(p, size); // Realloc failure case, deallocate original block @@ -162,7 +143,7 @@ void test_realloc(void* p, size_t size, int err, bool null) void test_posix_memalign(size_t size, size_t align, int err, bool null) { - printf("posix_memalign(&p, %zu, %zu)\n", align, size); + START_TEST("posix_memalign(&p, {}, {})", align, size); void* p = nullptr; errno = our_posix_memalign(&p, align, size); check_result(size, align, p, err, null); @@ -170,7 +151,7 @@ void test_posix_memalign(size_t size, size_t align, int err, bool null) void test_memalign(size_t size, size_t align, int err, bool null) { - printf("memalign(%zu, %zu)\n", align, size); + START_TEST("memalign({}, {})", align, size); errno = SUCCESS; void* p = our_memalign(align, size); check_result(size, align, p, err, null); @@ -183,7 +164,7 @@ void test_reallocarray(void* p, size_t nmemb, size_t size, int err, bool null) if (p != nullptr) old_size = our_malloc_usable_size(p); - printf("reallocarray(%p(%zu), %zu)\n", p, old_size, tsize); + START_TEST("reallocarray({}({}), {})", p, old_size, tsize); errno = SUCCESS; auto new_p = our_reallocarray(p, nmemb, size); if (new_p == nullptr && tsize != 0) @@ -198,15 +179,11 @@ void test_reallocarr( if (size_old != (size_t)~0) p = our_malloc(size_old); + START_TEST("reallocarr({}({}), {})", p, nmemb, size); errno = SUCCESS; int r = our_reallocarr(&p, nmemb, size); - if (r != err) - { - printf("reallocarr failed! expected %d got %d\n", err, r); - abort(); - } + EXPECT(r == err, "reallocarr failed! expected {} got {}\n", err, r); - printf("reallocarr(%p(%zu), %zu)\n", p, nmemb, size); check_result(nmemb * size, 1, p, err, null); p = our_malloc(size); if (!p) @@ -221,11 +198,7 @@ void test_reallocarr( for (size_t i = 1; i < size; i++) { - if (static_cast(p)[i] != 1) - { - printf("data consistency failed! at %zu", i); - abort(); - } + EXPECT(static_cast(p)[i] == 1, "data consistency failed! at {}", i); } our_free(p); } @@ -239,16 +212,23 @@ int main(int argc, char** argv) // Smoke test the fatal error builder. Check that it can generate strings // including all of the kinds of things that it expects to be able to format. + // + // Note: We cannot use the check or assert macros here because they depend on + // `MessageBuilder` working. They are safe to use in any other test. void* fakeptr = reinterpret_cast(static_cast(0x42)); - FatalErrorBuilder<1024> b{ - "testing pointer {} size_t {} message, {} world, null is {}", + MessageBuilder<1024> b{ + "testing pointer {} size_t {} message, {} world, null is {}, -123456 is " + "{}, 1234567 is {}", fakeptr, size_t(42), "hello", - nullptr}; + nullptr, + -123456, + 1234567}; if ( strcmp( - "testing pointer 0x42 size_t 0x2a message, hello world, null is 0x0", + "testing pointer 0x42 size_t 0x2a message, hello world, null is 0x0, " + "-123456 is -123456, 1234567 is 1234567", b.get_message()) != 0) { printf("Incorrect rendering of fatal error message: %s\n", b.get_message()); @@ -265,7 +245,7 @@ int main(int argc, char** argv) for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++) { const size_t size = bits::one_at_bit(sc); - printf("malloc: %zu\n", size); + START_TEST("malloc: {}", size); errno = SUCCESS; check_result(size, 1, our_malloc(size), SUCCESS, false); errno = SUCCESS; @@ -320,7 +300,7 @@ int main(int argc, char** argv) for (smallsizeclass_t sc2 = 0; sc2 < (MAX_SMALL_SIZECLASS_BITS + 4); sc2++) { const size_t size2 = bits::one_at_bit(sc2); - printf("size1: %zu, size2:%zu\n", size, size2); + INFO("size1: {}, size2:{}\n", size, size2); test_realloc(our_malloc(size), size2, SUCCESS, false); test_realloc(our_malloc(size + 1), size2, SUCCESS, false); } @@ -373,32 +353,22 @@ int main(int argc, char** argv) test_reallocarr(size, 1, 0, SUCCESS, false); test_reallocarr(size, 2, size, SUCCESS, false); void* p = our_malloc(size); - if (p == nullptr) - { - printf("realloc alloc failed with %zu\n", size); - abort(); - } + EXPECT(p != nullptr, "realloc alloc failed with {}", size); int r = our_reallocarr(&p, 1, too_big_size); - if (r != ENOMEM) - { - printf("expected failure on allocation\n"); - abort(); - } + EXPECT(r == ENOMEM, "expected failure on allocation\n"); our_free(p); for (smallsizeclass_t sc2 = 0; sc2 < (MAX_SMALL_SIZECLASS_BITS + 4); sc2++) { const size_t size2 = bits::one_at_bit(sc2); - printf("size1: %zu, size2:%zu\n", size, size2); + START_TEST("size1: {}, size2:{}", size, size2); test_reallocarr(size, 1, size2, SUCCESS, false); } } - if (our_malloc_usable_size(nullptr) != 0) - { - printf("malloc_usable_size(nullptr) should be zero"); - abort(); - } + EXPECT( + our_malloc_usable_size(nullptr) == 0, + "malloc_usable_size(nullptr) should be zero"); snmalloc::debug_check_empty(); return 0; diff --git a/src/test/func/memcpy/func-memcpy.cc b/src/test/func/memcpy/func-memcpy.cc index 202b303..03ce761 100644 --- a/src/test/func/memcpy/func-memcpy.cc +++ b/src/test/func/memcpy/func-memcpy.cc @@ -27,6 +27,7 @@ int main() # define my_free(x) free(x) # endif # include "override/memcpy.cc" +# include "test/helpers.h" # include # include @@ -65,6 +66,7 @@ extern "C" void abort() */ void check_size(size_t size) { + START_TEST("checking {}-byte memcpy", size); auto* s = reinterpret_cast(my_malloc(size + 1)); auto* d = reinterpret_cast(my_malloc(size + 1)); d[size] = 0; @@ -94,9 +96,13 @@ void check_size(size_t size) static_cast(i), dst[i]); } - SNMALLOC_CHECK(dst[i] == (unsigned char)i); + EXPECT( + dst[i] == (unsigned char)i, + "dst[i] == {}, i == {}", + size_t(dst[i]), + i & 0xff); } - SNMALLOC_CHECK(d[size] == 0); + EXPECT(d[size] == 0, "d[size] == {}", d[size]); } my_free(s); my_free(d); @@ -104,6 +110,8 @@ void check_size(size_t size) void check_bounds(size_t size, size_t out_of_bounds) { + START_TEST( + "memcpy bounds, size {}, {} bytes out of bounds", size, out_of_bounds); auto* s = reinterpret_cast(my_malloc(size)); auto* d = reinterpret_cast(my_malloc(size)); for (size_t i = 0; i < size; ++i) @@ -125,7 +133,11 @@ void check_bounds(size_t size, size_t out_of_bounds) bounds_error = true; } can_longjmp = false; - SNMALLOC_CHECK(bounds_error == (out_of_bounds > 0)); + EXPECT( + bounds_error == (out_of_bounds > 0), + "bounds error: {}, out_of_bounds: {}", + bounds_error, + out_of_bounds); my_free(s); my_free(d); } diff --git a/src/test/helpers.h b/src/test/helpers.h new file mode 100644 index 0000000..a4ea84d --- /dev/null +++ b/src/test/helpers.h @@ -0,0 +1,39 @@ +#pragma once +#ifdef _MSC_VER +# define __PRETTY_FUNCTION__ __FUNCSIG__ +#endif + +namespace snmalloc +{ + /** + * The name of the function under test. This is set in the START_TEST macro + * and used for error reporting in EXPECT. + */ + const char* current_test = ""; + + /** + * Log that the test started. + */ +#define START_TEST(msg, ...) \ + do \ + { \ + current_test = __PRETTY_FUNCTION__; \ + MessageBuilder<1024> mb{"Starting test: " msg "\n", ##__VA_ARGS__}; \ + fputs(mb.get_message(), stderr); \ + } while (0) + + /** + * An assertion that fires even in debug builds. Uses the value set by + * START_TEST. + */ +#define EXPECT(x, msg, ...) \ + SNMALLOC_CHECK_MSG(x, " in test {} " msg "\n", current_test, ##__VA_ARGS__) + +#define INFO(msg, ...) \ + do \ + { \ + MessageBuilder<1024> mb{msg "\n", ##__VA_ARGS__}; \ + fputs(mb.get_message(), stderr); \ + } while (0) + +}