aboutsummaryrefslogtreecommitdiffstats
path: root/libtest
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-05-08 22:29:35 +0200
committerMattias Andrée <m@maandree.se>2026-05-08 22:29:35 +0200
commit2d3a573977417d917c16742d8d9d8ead047d0ebc (patch)
treecaeac52856a9df0478e2bee53e5dda1f84422461 /libtest
parentAdd DEFAULT_SUPPORT option and improve DEPENDENCIES (diff)
downloadlibrecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.gz
librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.bz2
librecrypt-2d3a573977417d917c16742d8d9d8ead047d0ebc.tar.xz
Misc
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'libtest')
-rw-r--r--libtest/Makefile78
-rw-r--r--libtest/alloc.c505
-rw-r--r--libtest/alloc_have_custom.c236
-rw-r--r--libtest/common.h242
-rw-r--r--libtest/config.mk4
-rw-r--r--libtest/config_backtraces=false.mk10
-rw-r--r--libtest/config_backtraces=true.mk12
-rw-r--r--libtest/globals.c44
-rw-r--r--libtest/libtest.h23
-rw-r--r--libtest/libtest_alloc.c208
-rw-r--r--libtest/libtest_base_pointer.c20
-rw-r--r--libtest/libtest_check_no_leaks.c77
-rw-r--r--libtest/libtest_dump_stack.c34
-rw-r--r--libtest/libtest_expect_zeroed_on_free.c31
-rw-r--r--libtest/libtest_force_zero_on_alloc.c31
-rw-r--r--libtest/libtest_free.c94
-rw-r--r--libtest/libtest_get_pagesize.c37
-rw-r--r--libtest/libtest_print_backtrace.c122
-rw-r--r--libtest/libtest_start_tracking.c29
-rw-r--r--libtest/libtest_stop_tracking.c34
20 files changed, 1871 insertions, 0 deletions
diff --git a/libtest/Makefile b/libtest/Makefile
new file mode 100644
index 0000000..1bb870b
--- /dev/null
+++ b/libtest/Makefile
@@ -0,0 +1,78 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include ../mk/$(OS).mk
+
+
+OBJ =\
+ alloc.o\
+ alloc_have_custom.o\
+ globals.o\
+ libtest_alloc.o\
+ libtest_base_pointer.o\
+ libtest_check_no_leaks.o\
+ libtest_expect_zeroed_on_free.o\
+ libtest_force_zero_on_alloc.o\
+ libtest_free.o\
+ libtest_get_pagesize.o\
+ libtest_start_tracking.o\
+ libtest_stop_tracking.o\
+ libtest_dump_stack.o\
+ $(OBJ_BACKTRACE)
+
+HDR =\
+ libtest.h\
+ common.h\
+
+LOBJ = $(OBJ:.o=.lo)
+TOBJ = $(OBJ:.o=.to)
+TEST = $(OBJ:.o=.t)
+
+
+all: libtest.a $(TEST)
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+$(TOBJ): $(HDR)
+$(TEST): $(HDR) libtest.a
+
+.c.o:
+ $(C17) -fno-builtin -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS)
+
+.c.lo:
+ $(C17) -fno-builtin -fPIC -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS)
+
+.c.to:
+ $(C17) -DTEST -c -o $@ $< $(TEST_CFLAGS) $(TEST_CPPFLAGS)
+
+.to.t:
+ $(C17) -o $@ $< libtest.a $(TEST_LDFLAGS)
+
+.c.t:
+ $(C17) -DTEST -o $@ $< libtest.a $(TEST_CFLAGS) $(TEST_CPPFLAGS) $(TEST_LDFLAGS)
+
+libtest.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+check: $(TEST)
+ @set -ex;\
+ for t in $(TEST); do\
+ ./$$t;\
+ done
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT)
+ -rm -f -- *.to *.t
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c .to .t
+
+.PHONY: all check install uninstall clean
diff --git a/libtest/alloc.c b/libtest/alloc.c
new file mode 100644
index 0000000..842f856
--- /dev/null
+++ b/libtest/alloc.c
@@ -0,0 +1,505 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+static void *
+common_malloc(size_t n, enum memory_origin origin)
+{
+ struct meminfo meminfo;
+
+ assert(n);
+
+ memset(&meminfo, 0, sizeof(meminfo));
+ meminfo.requested_alloc_size = n;
+ meminfo.alignment_type = DEFAULT_ALIGNMENT;
+ meminfo.initialised_on_alloc = 0;
+ meminfo.origin = origin;
+
+ return libtest_alloc(&meminfo);
+}
+
+
+static void *
+common_realloc(void *old_ptr, size_t new_n, enum memory_origin origin)
+{
+ void *new_ptr;
+ size_t old_n;
+
+ assert(new_n);
+ if (!old_ptr)
+ return common_malloc(new_n, origin);
+
+ assert(GET_MEMINFO(old_ptr)->requested_alignment <= _Alignof(max_align_t));
+
+ new_ptr = common_malloc(new_n, origin); /* always change the pointer */
+ if (!new_ptr)
+ return NULL;
+
+ new_n = GET_MEMINFO(new_ptr)->usable_alloc_size;
+ old_n = GET_MEMINFO(old_ptr)->usable_alloc_size;
+ memcpy(new_ptr, old_ptr, new_n < old_n ? new_n : old_n);
+ libtest_free(old_ptr, DO_NOT_REQUIRE_ZEROED);
+
+ return new_ptr;
+}
+
+
+static void *
+common_memalign(size_t alignment, size_t size, enum memory_origin origin)
+{
+ struct meminfo meminfo;
+
+ assert(size);
+ assert(alignment);
+ assert(!(alignment & (alignment - 1u)));
+
+ memset(&meminfo, 0, sizeof(meminfo));
+ meminfo.requested_alloc_size = size;
+ meminfo.alignment_type = CUSTOM_ALIGNMENT;
+ meminfo.requested_alignment = alignment;
+ meminfo.initialised_on_alloc = 0;
+ meminfo.origin = origin;
+
+ return libtest_alloc(&meminfo);
+}
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wmissing-prototypes"
+#endif
+
+
+void *
+(malloc)(size_t n)
+{
+ libtest_malloc_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ return common_malloc(n, FROM_MALLOC);
+}
+
+
+void *
+(calloc)(size_t n, size_t m)
+{
+ struct meminfo meminfo;
+
+ libtest_calloc_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ assert(n);
+ assert(m);
+
+ if (n > SIZE_MAX / m) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ n *= m;
+
+ memset(&meminfo, 0, sizeof(meminfo));
+ meminfo.requested_alloc_size = n;
+ meminfo.alignment_type = DEFAULT_ALIGNMENT;
+ meminfo.initialised_on_alloc = 1;
+ meminfo.origin = FROM_CALLOC;
+
+ return libtest_alloc(&meminfo);
+}
+
+
+void *
+(realloc)(void *old_ptr, size_t new_n)
+{
+ libtest_realloc_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ return common_realloc(old_ptr, new_n, FROM_REALLOC);
+}
+
+
+void *
+(reallocarray)(void *old_ptr, size_t new_n, size_t new_m)
+{
+ libtest_reallocarray_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ assert(new_n);
+ assert(new_m);
+
+ if (new_n > SIZE_MAX / new_m) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return common_realloc(old_ptr, new_n * new_m, FROM_REALLOCARRAY);
+}
+
+
+void *
+(valloc)(size_t size)
+{
+ struct meminfo meminfo;
+
+ libtest_valloc_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ memset(&meminfo, 0, sizeof(meminfo));
+ meminfo.requested_alloc_size = size;
+ meminfo.alignment_type = PAGE_ALIGNMENT;
+ meminfo.initialised_on_alloc = 0;
+ meminfo.origin = FROM_VALLOC;
+
+ return libtest_alloc(&meminfo);
+}
+
+
+void *
+(pvalloc)(size_t size)
+{
+ struct meminfo meminfo;
+ void *ptr;
+ size_t pagesize;
+
+ libtest_pvalloc_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ pagesize = libtest_get_pagesize();
+ memset(&meminfo, 0, sizeof(meminfo));
+ meminfo.requested_alloc_size = size + (pagesize - size % pagesize) % pagesize;
+ meminfo.alignment_type = PAGE_ALIGNMENT;
+ meminfo.initialised_on_alloc = 0;
+ meminfo.origin = FROM_PVALLOC;
+
+ ptr = libtest_alloc(&meminfo);
+ if (ptr)
+ GET_MEMINFO(ptr)->requested_alloc_size = size;
+ return ptr;
+}
+
+
+void *
+(memalign)(size_t alignment, size_t size)
+{
+ libtest_memalign_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ return common_memalign(alignment, size, FROM_MEMALIGN);
+}
+
+
+void *
+(aligned_alloc)(size_t alignment, size_t size)
+{
+ libtest_aligned_alloc_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ assert(alignment);
+ assert(!(size % alignment));
+
+ return common_memalign(alignment, size, FROM_ALIGNED_ALLOC);
+}
+
+
+int
+(posix_memalign)(void **memptr, size_t alignment, size_t size)
+{
+ struct meminfo meminfo;
+ int err, saved_errno = errno;
+ void *ptr;
+
+ libtest_posix_memalign_is_custom = 1;
+ assert(libtest_have_custom_free());
+
+ assert(size);
+ assert(alignment);
+ assert(!(alignment & (alignment - 1u)));
+ assert(!(alignment & (sizeof(void *) - 1u)));
+
+ memset(&meminfo, 0, sizeof(meminfo));
+ meminfo.requested_alloc_size = size;
+ meminfo.alignment_type = CUSTOM_ALIGNMENT;
+ meminfo.requested_alignment = alignment;
+ meminfo.initialised_on_alloc = 0;
+ meminfo.origin = FROM_POSIX_MEMALIGN;
+
+ ptr = libtest_alloc(&meminfo);
+ err = ptr ? 0 : errno;
+ if (!err)
+ *memptr = ptr;
+
+ errno = saved_errno;
+ return err;
+}
+
+
+PURE
+size_t
+(malloc_usable_size)(void *ptr)
+{
+ libtest_malloc_usable_size_is_custom = 1;
+ assert(libtest_have_custom_malloc());
+
+ return ptr ? GET_MEMINFO(ptr)->usable_alloc_size : 0u;
+}
+
+
+void
+(free)(void *ptr)
+{
+ libtest_free_is_custom = 1;
+
+ if (ptr) {
+ struct meminfo *meminfo;
+ meminfo = GET_MEMINFO(ptr);
+ if (!meminfo->accept_leakage) {
+ assert(meminfo->origin != FROM_VALLOC);
+ assert(meminfo->origin != FROM_PVALLOC);
+ }
+ }
+
+ libtest_free(ptr, REQUIRE_ZEROED);
+}
+
+
+void
+(free_sized)(void *ptr, size_t size)
+{
+ libtest_free_sized_is_custom = 1;
+
+ if (ptr) {
+ struct meminfo *meminfo;
+ meminfo = GET_MEMINFO(ptr);
+ assert(meminfo->alignment_type == DEFAULT_ALIGNMENT);
+ assert(meminfo->requested_alloc_size == size);
+ }
+
+ libtest_free(ptr, REQUIRE_ZEROED);
+}
+
+
+void
+(free_aligned_sized)(void *ptr, size_t alignment, size_t size)
+{
+ libtest_free_aligned_sized_is_custom = 1;
+
+ if (ptr) {
+ struct meminfo *meminfo;
+ meminfo = GET_MEMINFO(ptr);
+ assert(meminfo->alignment_type == CUSTOM_ALIGNMENT);
+ assert(meminfo->requested_alloc_size == size);
+ assert(meminfo->requested_alignment == alignment);
+ }
+
+ libtest_free(ptr, REQUIRE_ZEROED);
+}
+
+
+#else
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wredundant-decls"
+#endif
+
+
+void *(malloc)(size_t n);
+void *(calloc)(size_t n, size_t m);
+void *(realloc)(void *old_ptr, size_t new_n);
+void *(reallocarray)(void *old_ptr, size_t new_n, size_t new_m);
+void *(valloc)(size_t size);
+void *(pvalloc)(size_t size);
+void *(memalign)(size_t alignment, size_t size);
+void *(aligned_alloc)(size_t alignment, size_t size);
+int (posix_memalign)(void **memptr, size_t alignment, size_t size);
+size_t (malloc_usable_size)(void *ptr);
+void (free)(void *ptr);
+void (free_sized)(void *ptr, size_t size);
+void (free_aligned_sized)(void *ptr, size_t alignment, size_t size);
+
+
+static void
+fill_memory(uint8_t *mem, size_t n)
+{
+ size_t i;
+ for (i = 0u; i < n; i++)
+ mem[i] = (uint8_t)i;
+}
+
+
+static int
+check_memory(uint8_t *mem, size_t filled, size_t zeroed)
+{
+ size_t i;
+ uint8_t bad = 0;
+ for (i = 0u; i < filled; i++)
+ bad |= (uint8_t)(mem[i] ^ (uint8_t)i);
+ for (i = filled; i < filled + zeroed; i++)
+ bad |= mem[i];
+ return !bad;
+}
+
+
+#define p libtest_p___
+extern void *volatile p;
+void *volatile p;
+
+
+static void
+check(int use_free)
+{
+ void *q;
+
+ p = malloc(10u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 10u);
+ assert(malloc_usable_size(p) >= 10u);
+ if (use_free)
+ free(p);
+ else
+ free_sized(p, 10u);
+
+ p = calloc(3u, 4u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 12u);
+ assert(malloc_usable_size(p) >= 12u);
+ if (use_free)
+ free(p);
+ else
+ free_sized(p, 12u);
+
+ p = realloc(NULL, 5u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 5u);
+ assert(malloc_usable_size(p) >= 5u);
+ fill_memory(p, 5u);
+
+ p = realloc(p, 9u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 9u);
+ assert(malloc_usable_size(p) >= 9u);
+ assert(check_memory(p, 5u, 4u));
+
+ p = realloc(p, 3u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 3u);
+ assert(malloc_usable_size(p) >= 3u);
+ assert(check_memory(p, 3u, 0u));
+ if (use_free)
+ free(p);
+ else
+ free_sized(p, 3u);
+
+ p = reallocarray(NULL, 3u, 10u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 30u);
+ assert(malloc_usable_size(p) >= 30u);
+ fill_memory(p, 30u);
+
+ p = reallocarray(p, 10u, 10u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 100u);
+ assert(malloc_usable_size(p) >= 100u);
+ assert(check_memory(p, 30u, 70u));
+
+ p = reallocarray(p, 5u, 10u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == _Alignof(max_align_t));
+ assert((uintptr_t)p % (uintptr_t)_Alignof(max_align_t) == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 50u);
+ assert(malloc_usable_size(p) >= 50u);
+ assert(check_memory(p, 30u, 20u));
+ if (use_free)
+ free(p);
+ else
+ free_sized(p, 50u);
+
+ p = memalign(2u, 7u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == 2u);
+ assert((uintptr_t)p % 2u == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 7u);
+ assert(malloc_usable_size(p) >= 7u);
+ if (use_free)
+ free(p);
+ else
+ free_aligned_sized(p, 2u, 7u);
+
+ p = aligned_alloc(2u, 4u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == 2u);
+ assert((uintptr_t)p % 2u == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 4u);
+ assert(malloc_usable_size(p) >= 4u);
+ if (use_free)
+ free(p);
+ else
+ free_aligned_sized(p, 2u, 4u);
+
+ assert(!posix_memalign(&q, sizeof(void *), 11u));
+ p = q;
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == sizeof(void *));
+ assert((uintptr_t)p % 2u == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 11u);
+ assert(malloc_usable_size(p) >= 11u);
+ if (use_free)
+ free(p);
+ else
+ free_aligned_sized(p, sizeof(void *), 11u);
+}
+
+
+int
+main(void)
+{
+ size_t pagesize;
+
+ SET_UP_ALARM();
+
+ libtest_start_tracking();
+
+ check(1);
+ check(0);
+
+ pagesize = libtest_get_pagesize();
+
+ libtest_stop_tracking();
+
+ p = valloc(6u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == pagesize);
+ assert((uintptr_t)p % (uintptr_t)pagesize == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 6u);
+ assert(malloc_usable_size(p) >= 6u);
+ /* cannot be free(3)ed */
+
+ p = pvalloc(8u);
+ assert(p);
+ assert(GET_MEMINFO(p)->requested_alignment == pagesize);
+ assert((uintptr_t)p % (uintptr_t)pagesize == 0u);
+ assert(GET_MEMINFO(p)->requested_alloc_size == 8u);
+ assert(malloc_usable_size(p) >= pagesize);
+ /* cannot be free(3)ed */
+
+ assert(libtest_check_no_leaks());
+ return 0;
+
+ /* TODO test failures */
+}
+
+
+#endif
diff --git a/libtest/alloc_have_custom.c b/libtest/alloc_have_custom.c
new file mode 100644
index 0000000..ae67be0
--- /dev/null
+++ b/libtest/alloc_have_custom.c
@@ -0,0 +1,236 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wredundant-decls"
+#endif
+
+void *(malloc)(size_t n);
+void *(calloc)(size_t n, size_t m);
+void *(realloc)(void *old_ptr, size_t new_n);
+void *(reallocarray)(void *old_ptr, size_t new_n, size_t new_m);
+void *(valloc)(size_t size);
+void *(pvalloc)(size_t size);
+void *(memalign)(size_t alignment, size_t size);
+void *(aligned_alloc)(size_t alignment, size_t size);
+int (posix_memalign)(void **memptr, size_t alignment, size_t size);
+PURE size_t (malloc_usable_size)(void *ptr);
+void (free)(void *ptr);
+void (free_sized)(void *ptr, size_t size);
+void (free_aligned_sized)(void *ptr, size_t alignment, size_t size);
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+
+#define CHECK_CUSTOM_ALLOC(WHAT, ...)\
+ do {\
+ int r = libtest_##WHAT##_is_custom;\
+ if (r < 0) {\
+ void *p = (WHAT)(__VA_ARGS__);\
+ free(p);\
+ r = libtest_##WHAT##_is_custom;\
+ if (r < 0)\
+ libtest_##WHAT##_is_custom = r = 0;\
+ }\
+ assert(r == 0 || r == 1);\
+ return r;\
+ } while (0)
+
+
+static void *
+malloc_with_realloc(size_t n)
+{
+ void *volatile ptr = malloc(1u);
+ assert(ptr);
+ ptr = realloc(ptr, n);
+ assert(ptr);
+ return ptr;
+}
+
+
+static void *
+malloc_with_reallocarray(size_t n, size_t m)
+{
+ void *volatile ptr = malloc(1u);
+ assert(ptr);
+ ptr = reallocarray(ptr, n, m);
+ assert(ptr);
+ return ptr;
+}
+
+
+static void *
+freeable_valloc(size_t n)
+{
+ void *ptr = valloc(n);
+ if (ptr) {
+ GET_MEMINFO(ptr)->origin = FROM_ALIGNED_ALLOC;
+ GET_MEMINFO(ptr)->alignment_type = CUSTOM_ALIGNMENT;
+ }
+ return ptr;
+}
+
+
+static void *
+freeable_pvalloc(size_t n)
+{
+ void *ptr = pvalloc(n);
+ if (ptr) {
+ GET_MEMINFO(ptr)->origin = FROM_ALIGNED_ALLOC;
+ GET_MEMINFO(ptr)->alignment_type = CUSTOM_ALIGNMENT;
+ }
+ return ptr;
+}
+
+
+#define libtest_malloc_with_realloc_is_custom libtest_realloc_is_custom
+#define libtest_malloc_with_reallocarray_is_custom libtest_reallocarray_is_custom
+#define libtest_freeable_valloc_is_custom libtest_valloc_is_custom
+#define libtest_freeable_pvalloc_is_custom libtest_pvalloc_is_custom
+int libtest_have_custom_malloc(void) { CHECK_CUSTOM_ALLOC(malloc, 1u); }
+int libtest_have_custom_calloc(void) { CHECK_CUSTOM_ALLOC(calloc, 1u, 1u); }
+int libtest_have_custom_realloc(void) { CHECK_CUSTOM_ALLOC(malloc_with_realloc, 1u); }
+int libtest_have_custom_reallocarray(void) { CHECK_CUSTOM_ALLOC(malloc_with_reallocarray, 1u, 1u); }
+int libtest_have_custom_valloc(void) { CHECK_CUSTOM_ALLOC(freeable_valloc, 1u); }
+int libtest_have_custom_pvalloc(void) { CHECK_CUSTOM_ALLOC(freeable_pvalloc, 1u); }
+int libtest_have_custom_memalign(void) { CHECK_CUSTOM_ALLOC(memalign, 1u, 1u); }
+int libtest_have_custom_aligned_alloc(void) { CHECK_CUSTOM_ALLOC(aligned_alloc, 1u, 1u); }
+
+
+int
+libtest_have_custom_posix_memalign(void)
+{
+ int r = libtest_posix_memalign_is_custom;
+ if (r < 0) {
+ void *p = NULL;
+ r = (posix_memalign)(&p, sizeof(p), 1u);
+ if (r)
+ free(p);
+ r = libtest_posix_memalign_is_custom;
+ if (r < 0)
+ libtest_posix_memalign_is_custom = r = 0;
+ }
+ assert(r == 0 || r == 1);
+ return r;
+}
+
+
+static volatile size_t libtest_have_custom_malloc_usable_size_discard;
+
+int
+libtest_have_custom_malloc_usable_size(void)
+{
+ int r = libtest_malloc_usable_size_is_custom;
+ if (r < 0) {
+ void *p = (malloc)(1u);
+ assert(p);
+ libtest_have_custom_malloc_usable_size_discard = malloc_usable_size(p);
+ (free)(p);
+ r = libtest_malloc_usable_size_is_custom;
+ if (r < 0)
+ libtest_malloc_usable_size_is_custom = r = 0;
+ }
+ assert(r == 0 || r == 1);
+ return r;
+}
+
+
+int
+libtest_have_custom_free(void)
+{
+ int r = libtest_free_is_custom;
+ void *p;
+ if (r < 0) {
+ libtest_free_is_custom = 1;
+ p = (malloc)(1u);
+ assert(p);
+ assert(libtest_malloc_is_custom == 1);
+ (free)(p);
+ r = libtest_free_is_custom;
+ if (r < 0)
+ libtest_free_is_custom = r = 0;
+ }
+ assert(r == 0 || r == 1);
+ return r;
+}
+
+
+int
+libtest_have_custom_free_sized(void)
+{
+ int r = libtest_free_sized_is_custom;
+ void *p;
+ if (r < 0) {
+ libtest_free_sized_is_custom = 1;
+ p = (malloc)(1u);
+ assert(p);
+ assert(libtest_malloc_is_custom == 1);
+ (free_sized)(p, 1u);
+ r = libtest_free_sized_is_custom;
+ if (r < 0)
+ libtest_free_sized_is_custom = r = 0;
+ }
+ assert(r == 0 || r == 1);
+ return r;
+}
+
+
+int
+libtest_have_custom_free_aligned_sized(void)
+{
+ int r = libtest_free_aligned_sized_is_custom;
+ void *p;
+ if (r < 0) {
+ libtest_free_aligned_sized_is_custom = 1;
+ p = (aligned_alloc)(1u, 1u);
+ assert(p);
+ assert(libtest_aligned_alloc_is_custom == 1);
+ (free_aligned_sized)(p, 1u, 1u);
+ r = libtest_free_aligned_sized_is_custom;
+ if (r < 0)
+ libtest_free_aligned_sized_is_custom = r = 0;
+ }
+ assert(r == 0 || r == 1);
+ return r;
+}
+
+
+#else
+
+
+#define CHECK(WHAT)\
+ do {\
+ fprintf(stderr, "testing %s\n", #WHAT);\
+ EXPECT(WHAT() == 1);\
+ } while (0)
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ CHECK(libtest_have_custom_malloc);
+ CHECK(libtest_have_custom_calloc);
+ CHECK(libtest_have_custom_realloc);
+ CHECK(libtest_have_custom_reallocarray);
+ CHECK(libtest_have_custom_valloc);
+ CHECK(libtest_have_custom_pvalloc);
+ CHECK(libtest_have_custom_memalign);
+ CHECK(libtest_have_custom_aligned_alloc);
+ CHECK(libtest_have_custom_posix_memalign);
+ CHECK(libtest_have_custom_malloc_usable_size);
+ CHECK(libtest_have_custom_free);
+ CHECK(libtest_have_custom_free_sized);
+ CHECK(libtest_have_custom_free_aligned_sized);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/common.h b/libtest/common.h
new file mode 100644
index 0000000..070b217
--- /dev/null
+++ b/libtest/common.h
@@ -0,0 +1,242 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef WITH_BACKTRACE
+# define UNW_LOCAL_ONLY
+# include <libunwind.h>
+# include <elfutils/libdwfl.h>
+#endif
+#include <sys/mman.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdatomic.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libtest.h"
+
+
+#if __STDC_VERSION__ >= 202311L
+# define _Alignof alignof
+# define _Alignas alignas
+# define _Thread_local thread_local
+#endif
+
+
+#if defined(__GNUC__)
+# define HIDDEN __attribute__((__visibility__("hidden")))
+# define CONST __attribute__((__const__))
+# define PURE __attribute__((__pure__))
+#else
+# define HIDDEN
+# define CONST
+# define PURE
+#endif
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wpadded"
+#endif
+
+
+#ifndef MAP_UNINITIALIZED
+# define MAP_UNINITIALIZED 0
+#endif
+
+
+#define ELEMSOF(A) (sizeof(A) / sizeof(*(A)))
+
+
+#define BASE_POINTER(PTR) (*libtest_base_pointer((PTR)))
+#define GET_MEMINFO(PTR) ((struct meminfo *)BASE_POINTER(PTR))
+#define SPINLOCK(LOCK) do {} while (atomic_flag_test_and_set_explicit(&(LOCK), memory_order_acquire))
+#define SPINUNLOCK(LOCK) atomic_flag_clear_explicit(&(LOCK), memory_order_release)
+
+
+enum libtest_zero_check {
+ DO_NOT_REQUIRE_ZEROED,
+ REQUIRE_ZEROED
+};
+
+enum align_type {
+ DEFAULT_ALIGNMENT,
+ PAGE_ALIGNMENT,
+ CUSTOM_ALIGNMENT
+};
+
+enum memory_origin {
+ FROM_MALLOC,
+ FROM_CALLOC,
+ FROM_REALLOC,
+ FROM_REALLOCARRAY,
+ FROM_VALLOC,
+ FROM_PVALLOC,
+ FROM_MEMALIGN,
+ FROM_ALIGNED_ALLOC,
+ FROM_POSIX_MEMALIGN,
+ FROM_MMAP_FILE,
+ FROM_MMAP_ANON
+};
+
+#ifdef WITH_BACKTRACE
+struct backtrace {
+ size_t n;
+ Dwarf_Addr trace[];
+};
+#endif
+
+struct meminfo {
+#ifdef WITH_BACKTRACE
+ struct backtrace *backtrace;
+#endif
+ struct meminfo *next, *prev;
+ void *usable_area;
+ size_t real_alloc_size;
+ size_t usable_alloc_size;
+ size_t requested_alloc_size;
+ size_t requested_alignment;
+ size_t actual_alignment;
+ enum align_type alignment_type;
+ int initialised_on_alloc;
+ enum memory_origin origin;
+ int accept_leakage; /* implies DO_NOT_REQUIRE_ZEROED */
+};
+
+
+extern volatile int libtest_malloc_is_custom;
+extern volatile int libtest_calloc_is_custom;
+extern volatile int libtest_realloc_is_custom;
+extern volatile int libtest_reallocarray_is_custom;
+extern volatile int libtest_valloc_is_custom;
+extern volatile int libtest_pvalloc_is_custom;
+extern volatile int libtest_memalign_is_custom;
+extern volatile int libtest_aligned_alloc_is_custom;
+extern volatile int libtest_posix_memalign_is_custom;
+extern volatile int libtest_malloc_usable_size_is_custom;
+extern volatile int libtest_free_is_custom;
+extern volatile int libtest_free_sized_is_custom;
+extern volatile int libtest_free_aligned_sized_is_custom;
+
+extern struct meminfo libtest_allocs_head;
+extern struct meminfo libtest_allocs_tail;
+extern int libtest_allocs_list_inited;
+extern atomic_flag libtest_allocs_list_spinlock;
+
+extern int libtest_zero_on_alloc;
+extern int libtest_expect_zeroed;
+extern int libtest_malloc_accept_leakage;
+
+extern _Thread_local size_t libtest_malloc_internal_usage;
+extern _Thread_local size_t libtest_kill_malloc_tracking;
+
+
+HIDDEN inline void **
+libtest_base_pointer(void *ptr)
+{
+ uintptr_t addr = (uintptr_t)ptr - (uintptr_t)sizeof(void *);
+ addr -= addr % _Alignof(void *);
+ return (void **)addr;
+}
+
+
+HIDDEN size_t libtest_get_pagesize(void);
+HIDDEN void *libtest_alloc(struct meminfo *);
+HIDDEN void libtest_free(void *, enum libtest_zero_check);
+
+#ifdef WITH_BACKTRACE
+HIDDEN void libtest_print_backtrace(FILE *, const char *indent, size_t first, const struct backtrace *);
+#else
+# define libtest_print_backtrace(FP, INDENT, FIRST, BACKTRACE) ((void)0)
+#endif
+
+
+void *__mmap(void *, size_t, int, int, int, off_t);
+int __munmap(void *, size_t);
+void *__mremap(void *, size_t, size_t, int, ...);
+#define libtest_real_mmap __mmap
+#define libtest_real_munmap __munmap
+#define libtest_real_mremap __mremap
+
+
+#define assert(EXPR)\
+ do {\
+ if (!(EXPR)) {\
+ libtest_malloc_internal_usage++;\
+ fprintf(stderr, "Assetion failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\
+ libtest_print_backtrace(stderr, "\t", 0u, NULL);\
+ exit(2);\
+ }\
+ } while (0)
+
+
+#ifdef TEST
+# ifdef __linux__
+# include <sys/prctl.h>
+# endif
+# include <sys/resource.h>
+# include <sys/types.h>
+# include <sys/wait.h>
+# include <signal.h>
+# include <string.h>
+# include <unistd.h>
+
+# define SET_UP_ALARM()\
+ do {\
+ unsigned int alarm_time__ = alarm(10u);\
+ if (alarm_time__ > 10u)\
+ alarm(alarm_time__);\
+ } while (0)
+
+# if defined(PR_SET_DUMPABLE)
+# define INIT_TEST_ABORT()\
+ do {\
+ struct rlimit rl__;\
+ rl__.rlim_cur = 0;\
+ rl__.rlim_max = 0;\
+ (void) setrlimit(RLIMIT_CORE, &rl__);\
+ (void) prctl(PR_SET_DUMPABLE, 0);\
+ EXPECT_ABORT(abort());\
+ } while (0)
+# else
+# define INIT_TEST_ABORT()\
+ do {\
+ struct rlimit rl__;\
+ rl__.rlim_cur = 0;\
+ rl__.rlim_max = 0;\
+ (void) setrlimit(RLIMIT_CORE, &rl__);\
+ EXPECT_ABORT(abort());\
+ } while (0)
+# endif
+
+# define EXPECT__(EXPR, HOW, RETEXTRACT, RETEXPECT)\
+ do {\
+ pid_t pid__;\
+ int status__;\
+ pid__ = fork();\
+ EXPECT(pid__ != -1);\
+ if (pid__ == 0) {\
+ (EXPR);\
+ _exit(0);\
+ }\
+ EXPECT(waitpid(pid__, &status__, 0) == pid__);\
+ EXPECT(HOW(status__));\
+ EXPECT(RETEXTRACT(status__) == RETEXPECT);\
+ } while (0)
+
+# define EXPECT_ABORT(EXPR)\
+ do {\
+ EXPECT__(EXPR, WIFSIGNALED, WTERMSIG, SIGABRT);\
+ } while (0)
+
+# define EXPECT(EXPR)\
+ do {\
+ if (!(EXPR)) {\
+ libtest_malloc_internal_usage++;\
+ fprintf(stderr, "Failure at %s:%i: %s\n", __FILE__, __LINE__, #EXPR);\
+ libtest_print_backtrace(stderr, "\t", 0u, NULL);\
+ exit(1);\
+ }\
+ } while (0)
+#endif
diff --git a/libtest/config.mk b/libtest/config.mk
new file mode 100644
index 0000000..b2519f0
--- /dev/null
+++ b/libtest/config.mk
@@ -0,0 +1,4 @@
+WITH_BACKTRACE = true
+
+TEST_CONFIGFILE = config_backtraces=$(WITH_BACKTRACE).mk
+include $(TEST_INCLUDE_PREFIX)$(TEST_CONFIGFILE)
diff --git a/libtest/config_backtraces=false.mk b/libtest/config_backtraces=false.mk
new file mode 100644
index 0000000..f508390
--- /dev/null
+++ b/libtest/config_backtraces=false.mk
@@ -0,0 +1,10 @@
+C17 !=\
+ if command -v c17 >/dev/null || ! command -v cc >/dev/null; then\
+ echo c17;\
+ else\
+ echo cc -std=c17;\
+ fi
+
+TEST_CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+TEST_CFLAGS =
+TEST_LDFLAGS =
diff --git a/libtest/config_backtraces=true.mk b/libtest/config_backtraces=true.mk
new file mode 100644
index 0000000..8233aaa
--- /dev/null
+++ b/libtest/config_backtraces=true.mk
@@ -0,0 +1,12 @@
+C17 !=\
+ if command -v c17 >/dev/null || ! command -v cc >/dev/null; then\
+ echo c17;\
+ else\
+ echo cc -std=c17;\
+ fi
+
+TEST_CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -DWITH_BACKTRACE
+TEST_CFLAGS = -g
+TEST_LDFLAGS = -lunwind -ldw
+
+OBJ_BACKTRACE = libtest_print_backtrace.o
diff --git a/libtest/globals.c b/libtest/globals.c
new file mode 100644
index 0000000..9e9ec2f
--- /dev/null
+++ b/libtest/globals.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+volatile int libtest_malloc_is_custom = -1;
+volatile int libtest_calloc_is_custom = -1;
+volatile int libtest_realloc_is_custom = -1;
+volatile int libtest_reallocarray_is_custom = -1;
+volatile int libtest_valloc_is_custom = -1;
+volatile int libtest_pvalloc_is_custom = -1;
+volatile int libtest_memalign_is_custom = -1;
+volatile int libtest_aligned_alloc_is_custom = -1;
+volatile int libtest_posix_memalign_is_custom = -1;
+volatile int libtest_malloc_usable_size_is_custom = -1;
+volatile int libtest_free_is_custom = -1;
+volatile int libtest_free_sized_is_custom = -1;
+volatile int libtest_free_aligned_sized_is_custom = -1;
+
+struct meminfo libtest_allocs_head;
+struct meminfo libtest_allocs_tail;
+int libtest_allocs_list_inited = 0;
+atomic_flag libtest_allocs_list_spinlock = ATOMIC_FLAG_INIT;
+
+int libtest_zero_on_alloc = 0;
+int libtest_expect_zeroed = 0;
+int libtest_malloc_accept_leakage = 1;
+
+_Thread_local size_t libtest_malloc_internal_usage = 0u;
+_Thread_local size_t libtest_kill_malloc_tracking = 0u;
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ /* There isn't really anything to test here */
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest.h b/libtest/libtest.h
new file mode 100644
index 0000000..3a3d963
--- /dev/null
+++ b/libtest/libtest.h
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+
+void libtest_start_tracking(void);
+void libtest_stop_tracking(void);
+int libtest_check_no_leaks(void);
+void libtest_force_zero_on_alloc(int);
+void libtest_expect_zeroed_on_free(int);
+
+int libtest_have_custom_malloc(void);
+int libtest_have_custom_calloc(void);
+int libtest_have_custom_realloc(void);
+int libtest_have_custom_reallocarray(void);
+int libtest_have_custom_valloc(void);
+int libtest_have_custom_pvalloc(void);
+int libtest_have_custom_memalign(void);
+int libtest_have_custom_aligned_alloc(void);
+int libtest_have_custom_posix_memalign(void);
+int libtest_have_custom_malloc_usable_size(void);
+int libtest_have_custom_free(void);
+int libtest_have_custom_free_sized(void);
+int libtest_have_custom_free_aligned_sized(void);
+
+void libtest_dump_stack(const char *indent);
diff --git a/libtest/libtest_alloc.c b/libtest/libtest_alloc.c
new file mode 100644
index 0000000..5aa3218
--- /dev/null
+++ b/libtest/libtest_alloc.c
@@ -0,0 +1,208 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(WITH_BACKTRACE) && defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Walloca"
+#endif
+
+
+static _Thread_local int inside_mmap_anon = 0;
+
+
+static void
+meminfo_fixup(struct meminfo *meminfo)
+{
+ if (meminfo->alignment_type == DEFAULT_ALIGNMENT)
+ meminfo->requested_alignment = _Alignof(max_align_t);
+ else if (meminfo->alignment_type == PAGE_ALIGNMENT)
+ meminfo->requested_alignment = libtest_get_pagesize();
+
+ meminfo->actual_alignment = meminfo->requested_alignment;
+ meminfo->accept_leakage = !!libtest_malloc_accept_leakage;
+
+ if (libtest_zero_on_alloc)
+ meminfo->initialised_on_alloc |= !meminfo->accept_leakage;
+}
+
+
+static int
+add_or_enomem(size_t *augend, size_t augment)
+{
+ if (*augend > SIZE_MAX - augment) {
+ errno = ENOMEM;
+ return -1;
+ }
+ *augend += augment;
+ return 0;
+}
+
+
+static void *
+mmap_anon(size_t size)
+{
+ void *ptr;
+ inside_mmap_anon = 1;
+ ptr = libtest_real_mmap(NULL, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED,
+ -1, 0);
+ inside_mmap_anon = 0;
+ return ptr == MAP_FAILED ? NULL : ptr;
+}
+
+
+void *
+libtest_alloc(struct meminfo *meminfo)
+{
+ static _Thread_local int recursion_guard = 0;
+ void *base_ptr, *ret_ptr;
+ size_t misalignment;
+ size_t bookkeeping;
+ int saved_errno;
+#ifdef WITH_BACKTRACE
+ size_t backtrace_realignment;
+ size_t i, backtrace_n;
+ unw_cursor_t cursor;
+ unw_context_t context;
+ unw_word_t rip;
+ struct partial {
+ uintptr_t rips[8];
+ struct partial *next;
+ } *current, head;
+#endif
+
+ assert(!inside_mmap_anon);
+
+ saved_errno = errno;
+ meminfo_fixup(meminfo);
+#ifdef WITH_BACKTRACE
+ meminfo->accept_leakage |= libtest_malloc_internal_usage > 0;
+#endif
+
+ /* Get backtrace (have to do it now to calculate `backtrace_n` for allocation) */
+#ifdef WITH_BACKTRACE
+ backtrace_n = 0u;
+ if (!libtest_malloc_accept_leakage && !libtest_malloc_internal_usage) {
+ libtest_malloc_internal_usage++;
+ if (unw_getcontext(&context) || unw_init_local(&cursor, &context))
+ goto skip_backtrace;
+ for (current = &head, i = 0u; unw_step(&cursor) > 0; i++) {
+ if (i % ELEMSOF(current->rips) == 0u)
+ current = current->next = alloca(sizeof(*current));
+ if (unw_get_reg(&cursor, UNW_REG_IP, &rip))
+ goto skip_backtrace;
+ current->rips[i % ELEMSOF(current->rips)] = (uintptr_t)rip;
+ }
+ backtrace_n = i;
+ skip_backtrace:
+ libtest_malloc_internal_usage--;
+ }
+#endif
+
+ /* Calculate required alloction size */
+ meminfo->real_alloc_size = meminfo->requested_alloc_size;
+ if (add_or_enomem(&meminfo->real_alloc_size, sizeof(struct meminfo)) ||
+ add_or_enomem(&meminfo->real_alloc_size, _Alignof(void *) + sizeof(void *)) ||
+ add_or_enomem(&meminfo->real_alloc_size, meminfo->actual_alignment))
+ return NULL;
+#ifdef WITH_BACKTRACE
+ if (backtrace_n) {
+ misalignment = sizeof(struct meminfo) % _Alignof(struct backtrace);
+ backtrace_realignment = misalignment ? _Alignof(struct backtrace) - misalignment : 0u;
+ if (add_or_enomem(&meminfo->real_alloc_size, backtrace_realignment) ||
+ add_or_enomem(&meminfo->real_alloc_size, offsetof(struct backtrace, trace)) ||
+ add_or_enomem(&meminfo->real_alloc_size, backtrace_n * sizeof(Dwarf_Addr)))
+ return NULL;
+ }
+#endif
+ bookkeeping = meminfo->real_alloc_size - meminfo->requested_alloc_size;
+ bookkeeping -= meminfo->actual_alignment;
+
+ /* Allocate memory */
+ base_ptr = mmap_anon(meminfo->real_alloc_size);
+ if (!base_ptr)
+ return NULL;
+ if (meminfo->initialised_on_alloc)
+ memset(base_ptr, 0, meminfo->real_alloc_size);
+ ret_ptr = &((char *)base_ptr)[bookkeeping];
+
+ /* Save backtrace */
+#ifdef WITH_BACKTRACE
+ if (backtrace_n) {
+ meminfo->backtrace = (void *)&((char *)base_ptr)[sizeof(struct meminfo) + backtrace_realignment];
+ meminfo->backtrace->n = backtrace_n;
+ for (current = &head, i = 0u; i < backtrace_n; i++) {
+ if (i % ELEMSOF(current->rips) == 0u)
+ current = current->next;
+ meminfo->backtrace->trace[i] = current->rips[i % ELEMSOF(current->rips)];
+ }
+ } else {
+ meminfo->backtrace = NULL;
+ }
+#endif
+
+ /* Fix alignment */
+ misalignment = (size_t)(uintptr_t)ret_ptr % meminfo->actual_alignment;
+ if (misalignment)
+ ret_ptr = &((char *)ret_ptr)[meminfo->actual_alignment - misalignment];
+
+ /* Store usable size */
+ meminfo->usable_alloc_size = meminfo->real_alloc_size;
+ meminfo->usable_alloc_size -= (size_t)((char *)ret_ptr - (char *)base_ptr);
+
+ /* Store book-keeping */
+ meminfo->usable_area = ret_ptr;
+ memcpy(base_ptr, meminfo, sizeof(*meminfo));
+ BASE_POINTER(ret_ptr) = base_ptr;
+ meminfo = base_ptr;
+
+ /* Track allocation */
+ if (!libtest_kill_malloc_tracking) {
+ assert(!recursion_guard);
+ recursion_guard = 1;
+ SPINLOCK(libtest_allocs_list_spinlock);
+ if (!libtest_allocs_list_inited) {
+ libtest_allocs_head.prev = NULL;
+ libtest_allocs_head.next = &libtest_allocs_tail;
+ libtest_allocs_tail.prev = &libtest_allocs_head;
+ libtest_allocs_tail.next = NULL;
+ libtest_allocs_list_inited = 1;
+ }
+ libtest_allocs_tail.prev->next = meminfo;
+ meminfo->prev = libtest_allocs_tail.prev;
+ meminfo->next = &libtest_allocs_tail;
+ SPINUNLOCK(libtest_allocs_list_spinlock);
+ recursion_guard = 0;
+ }
+
+ /* Optionally print out trace */
+#ifdef WITH_BACKTRACE
+ if (meminfo->backtrace && !libtest_malloc_internal_usage && getenv("TRACE_MALLOC")) {
+ libtest_malloc_internal_usage++;
+ fprintf(stderr, "Memory allocated: %p (alloc-size=%zu, real-size=%zu, leak-allowed=%i)\n",
+ ret_ptr, meminfo->requested_alloc_size, meminfo->real_alloc_size, meminfo->accept_leakage);
+ libtest_print_backtrace(stderr, "\tat ", 0u, NULL);
+ fflush(stderr);
+ libtest_malloc_internal_usage--;
+ }
+#endif
+
+ errno = saved_errno;
+ return ret_ptr;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ /* Tested via alloc.c */
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_base_pointer.c b/libtest/libtest_base_pointer.c
new file mode 100644
index 0000000..4902643
--- /dev/null
+++ b/libtest/libtest_base_pointer.c
@@ -0,0 +1,20 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+extern inline void **libtest_base_pointer(void *);
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ /* This one isn't that simple to test, but it works if other tests work :) */
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_check_no_leaks.c b/libtest/libtest_check_no_leaks.c
new file mode 100644
index 0000000..15f7e58
--- /dev/null
+++ b/libtest/libtest_check_no_leaks.c
@@ -0,0 +1,77 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+extern int libtest_suppress_leak_output;
+#ifndef TEST
+
+
+int libtest_suppress_leak_output = 0;
+
+
+static int
+check_no_memory_leaks(void)
+{
+ struct meminfo *mem;
+ int no_leaks = 1;
+
+ SPINLOCK(libtest_allocs_list_spinlock);
+
+ if (!libtest_allocs_list_inited)
+ goto out;
+
+ libtest_kill_malloc_tracking++;
+ libtest_malloc_internal_usage++;
+ for (mem = libtest_allocs_head.next; mem->next; mem = mem->next) {
+ if (mem->accept_leakage)
+ continue;
+ no_leaks = 0;
+ if (libtest_suppress_leak_output)
+ continue;
+ fprintf(stderr, "Memory leak: %p (alloc-size=%zu)\n",
+ mem->usable_area, mem->requested_alloc_size);
+#ifdef WITH_BACKTRACE
+ if (mem->backtrace)
+ libtest_print_backtrace(stderr, "\tat ", 0u, mem->backtrace);
+#endif
+ fflush(stderr);
+ }
+ libtest_malloc_internal_usage--;
+ libtest_kill_malloc_tracking--;
+
+out:
+ SPINUNLOCK(libtest_allocs_list_spinlock);
+ return no_leaks;
+}
+
+
+int
+libtest_check_no_leaks(void)
+{
+ /* TODO check file descriptor leaks */
+ return check_no_memory_leaks();
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ void *p;
+
+ SET_UP_ALARM();
+
+ libtest_start_tracking();
+ assert(p = malloc(1u));
+ libtest_suppress_leak_output = 1;
+ EXPECT(!libtest_check_no_leaks());
+ libtest_suppress_leak_output = 0;
+ free(p);
+ libtest_stop_tracking();
+ EXPECT(libtest_check_no_leaks());
+
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_dump_stack.c b/libtest/libtest_dump_stack.c
new file mode 100644
index 0000000..ca0c9c7
--- /dev/null
+++ b/libtest/libtest_dump_stack.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#ifndef WITH_BACKTRACE
+# if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wattributes"
+# endif
+CONST
+#endif
+void
+libtest_dump_stack(const char *indent)
+{
+#ifndef WITH_BACKTRACE
+ (void) indent;
+#else
+ libtest_print_backtrace(stderr, indent, 1u, NULL);
+#endif
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ /* How would one even test this, and what would be the point? */
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_expect_zeroed_on_free.c b/libtest/libtest_expect_zeroed_on_free.c
new file mode 100644
index 0000000..959ad07
--- /dev/null
+++ b/libtest/libtest_expect_zeroed_on_free.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libtest_expect_zeroed_on_free(int v)
+{
+ libtest_expect_zeroed = v;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ EXPECT(libtest_expect_zeroed == 0);
+ libtest_expect_zeroed_on_free(1);
+ EXPECT(libtest_expect_zeroed == 1);
+ libtest_expect_zeroed_on_free(0);
+ EXPECT(libtest_expect_zeroed == 0);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_force_zero_on_alloc.c b/libtest/libtest_force_zero_on_alloc.c
new file mode 100644
index 0000000..907eabc
--- /dev/null
+++ b/libtest/libtest_force_zero_on_alloc.c
@@ -0,0 +1,31 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libtest_force_zero_on_alloc(int v)
+{
+ libtest_zero_on_alloc = v;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ EXPECT(libtest_zero_on_alloc == 0);
+ libtest_force_zero_on_alloc(1);
+ EXPECT(libtest_zero_on_alloc == 1);
+ libtest_force_zero_on_alloc(0);
+ EXPECT(libtest_zero_on_alloc == 0);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_free.c b/libtest/libtest_free.c
new file mode 100644
index 0000000..398994b
--- /dev/null
+++ b/libtest/libtest_free.c
@@ -0,0 +1,94 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libtest_free(void *ptr, enum libtest_zero_check zero_checking)
+{
+ struct meminfo *mem;
+ int saved_errno = errno;
+ int unmap_err, memory_zeroed;
+ uint8_t *usable_area;
+ size_t i;
+#ifdef WITH_BACKTRACE
+ static _Thread_local int inside_free = 0;
+#endif
+
+ /* free(3) can be called with NULL */
+ if (!ptr)
+ return;
+
+ /* Optionally print out trace */
+#ifdef WITH_BACKTRACE
+ if (!inside_free && getenv("PRETRACE_FREE")) {
+ inside_free = 1;
+ fprintf(stderr, "Deallocating: %p\n", ptr);
+ libtest_print_backtrace(stderr, "\tat ", 0u, NULL);
+ fflush(stderr);
+ inside_free = 0;
+ }
+#endif
+
+ /* Get the start of the allocation, which
+ * is where the book-keeping is located */
+ mem = GET_MEMINFO(ptr);
+
+ /* It is illegal to use free(3) on memory allocated with mmap(3)/mmap(2) */
+ assert(mem->origin != FROM_MMAP_FILE);
+ assert(mem->origin != FROM_MMAP_ANON);
+
+ /* Delist allocation */
+ if (!libtest_kill_malloc_tracking) {
+ SPINLOCK(libtest_allocs_list_spinlock);
+ mem->prev->next = mem->next;
+ mem->next->prev = mem->prev;
+ SPINUNLOCK(libtest_allocs_list_spinlock);
+ }
+
+ /* Check memory is zeroed */
+ if (zero_checking && libtest_expect_zeroed && !mem->accept_leakage) {
+ usable_area = mem->usable_area;
+ memory_zeroed = 1;
+ for (i = 0u; i < mem->usable_alloc_size; i++) {
+ if (usable_area[i]) {
+ memory_zeroed = 0;
+ break;
+ }
+ }
+ assert(memory_zeroed);
+ }
+
+ /* Optionally print out trace */
+#ifdef WITH_BACKTRACE
+ if (!inside_free && getenv("TRACE_MALLOC")) {
+ inside_free = 1;
+ fprintf(stderr, "Memory deallocated: %p\n (alloc-size=%zu, real-size=%zu)",
+ ptr, mem->requested_alloc_size, mem->real_alloc_size);
+ if (getenv("TRACE_FREE") && !getenv("PRETRACE_FREE"))
+ libtest_print_backtrace(stderr, "\tat ", 0u, NULL);
+ fflush(stderr);
+ inside_free = 0;
+ }
+#endif
+
+ /* Deallocate memory */
+ unmap_err = libtest_real_munmap(mem, mem->real_alloc_size);
+ assert(!unmap_err);
+
+ errno = saved_errno;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ /* Tested via alloc.c */
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_get_pagesize.c b/libtest/libtest_get_pagesize.c
new file mode 100644
index 0000000..52adbdc
--- /dev/null
+++ b/libtest/libtest_get_pagesize.c
@@ -0,0 +1,37 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+size_t
+libtest_get_pagesize(void)
+{
+ long int r;
+ int saved_errno = errno;
+ if ((r = sysconf(_SC_PAGESIZE)) <= 0)
+ if ((r = sysconf(_SC_PAGE_SIZE)) <= 0)
+ r = 4096L;
+ errno = saved_errno;
+ return (size_t)r;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+#ifdef __linux__
+ errno = 0;
+ EXPECT(libtest_get_pagesize() == 4096u);
+ EXPECT(!errno);
+#endif
+
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_print_backtrace.c b/libtest/libtest_print_backtrace.c
new file mode 100644
index 0000000..8fa0fd5
--- /dev/null
+++ b/libtest/libtest_print_backtrace.c
@@ -0,0 +1,122 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+#if defined(__linux__)
+# define HAVE_LINE_INFO
+#endif
+
+
+void
+libtest_print_backtrace(FILE *fp, const char *indent, size_t first, const struct backtrace *backtrace)
+{
+ static _Thread_local int recursion_guard = 0;
+ int saved_errno;
+ unw_word_t rip;
+ unw_cursor_t cursor;
+ unw_context_t context;
+ Dwarf_Addr ip;
+ size_t i;
+#if defined(HAVE_LINE_INFO)
+ Dwfl_Callbacks callbacks;
+ char *debuginfo_path = NULL;
+ Dwfl *dwfl = NULL;
+ Dwfl_Line *line = NULL;
+ Dwfl_Module *module = NULL;
+ int lineno = 0; /* initialised for compiler happiness */
+ const char *filename = NULL;
+ const char *funcname = NULL;
+#endif
+
+ if (recursion_guard)
+ return;
+ saved_errno = errno;
+ recursion_guard = 1;
+ libtest_malloc_internal_usage++;
+
+ if (!backtrace) {
+ if (unw_getcontext(&context))
+ goto out;
+ if (unw_init_local(&cursor, &context))
+ goto out;
+ }
+
+#if defined(__linux__)
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.find_elf = &dwfl_linux_proc_find_elf;
+ callbacks.find_debuginfo = &dwfl_standard_find_debuginfo;
+ callbacks.section_address = NULL;
+ callbacks.debuginfo_path = &debuginfo_path;
+
+ dwfl = dwfl_begin(&callbacks);
+ if (dwfl) {
+ if (dwfl_linux_proc_report(dwfl, getpid()) ||
+ dwfl_report_end(dwfl, NULL, NULL)) {
+ dwfl_end(dwfl);
+ dwfl = NULL;
+ }
+ }
+#endif
+
+ for (i = 0u; backtrace ? i < backtrace->n : unw_step(&cursor) > 0; i++) {
+ if (backtrace) {
+ ip = backtrace->trace[i];
+ } else {
+ if (unw_get_reg(&cursor, UNW_REG_IP, &rip))
+ break;
+ ip = (Dwarf_Addr)rip;
+ }
+ if (i < first)
+ continue;
+
+#if defined(HAVE_LINE_INFO)
+ if (dwfl) {
+ module = dwfl_addrmodule(dwfl, ip);
+ funcname = module ? dwfl_module_addrname(module, ip) : NULL;
+ line = dwfl_getsrc(dwfl, ip);
+ if (line) {
+ filename = dwfl_lineinfo(line, &(Dwarf_Addr){0}, &lineno, NULL, NULL, NULL);
+# ifdef USE_BASENAMES_IN_BACKTRACES
+ if (strrchr(filename, '/'))
+ filename = &strrchr(filename, '/')[1];
+# endif
+ }
+ }
+#endif
+
+#if defined(HAVE_LINE_INFO)
+ fprintf(fp, "%s0x%016"PRIxPTR": %s", indent, (uintptr_t)ip, funcname ? funcname : "???");
+ if (line)
+ fprintf(fp, " (%s:%i)\n", filename, lineno);
+ else
+ fprintf(fp, "\n");
+#else
+ fprintf(fp, "%s0x%016"PRIxPTR"\n", indent, (uintptr_t)ip);
+#endif
+ }
+
+#if defined(HAVE_LINE_INFO)
+ if (dwfl)
+ dwfl_end(dwfl);
+#endif
+
+out:
+ libtest_malloc_internal_usage--;
+ recursion_guard = 0;
+ errno = saved_errno;
+}
+
+
+#else
+
+
+CONST int
+main(void)
+{
+ /* How would one even test this, and what would be the point? */
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_start_tracking.c b/libtest/libtest_start_tracking.c
new file mode 100644
index 0000000..070fab9
--- /dev/null
+++ b/libtest/libtest_start_tracking.c
@@ -0,0 +1,29 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libtest_start_tracking(void)
+{
+ libtest_malloc_accept_leakage = 0;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ EXPECT(libtest_malloc_accept_leakage == 1);
+ libtest_start_tracking();
+ EXPECT(libtest_malloc_accept_leakage == 0);
+
+ return 0;
+}
+
+
+#endif
diff --git a/libtest/libtest_stop_tracking.c b/libtest/libtest_stop_tracking.c
new file mode 100644
index 0000000..29bb2aa
--- /dev/null
+++ b/libtest/libtest_stop_tracking.c
@@ -0,0 +1,34 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+
+void
+libtest_stop_tracking(void)
+{
+ libtest_malloc_accept_leakage = 1;
+}
+
+
+#else
+
+
+int
+main(void)
+{
+ SET_UP_ALARM();
+
+ EXPECT(libtest_malloc_accept_leakage == 1);
+
+ libtest_malloc_accept_leakage = 0;
+
+ EXPECT(libtest_malloc_accept_leakage == 0);
+
+ libtest_stop_tracking();
+ EXPECT(libtest_malloc_accept_leakage == 1);
+
+ return 0;
+}
+
+
+#endif