diff --git a/tests/malloc_thread_safety/Makefile b/tests/malloc_thread_safety/Makefile index 21e686cefe..7a6fa50d84 100644 --- a/tests/malloc_thread_safety/Makefile +++ b/tests/malloc_thread_safety/Makefile @@ -1,5 +1,9 @@ include ../Makefile.tests_common +PICOLIBC ?= 0 +ifneq (0,$(PICOLIBC)) + FEATURES_REQUIRED += picolibc +endif USEMODULE += xtimer include $(RIOTBASE)/Makefile.include diff --git a/tests/malloc_thread_safety/main.c b/tests/malloc_thread_safety/main.c index 8c88e5086a..1570fb1f33 100644 --- a/tests/malloc_thread_safety/main.c +++ b/tests/malloc_thread_safety/main.c @@ -39,7 +39,7 @@ static char WORD_ALIGNED t1_stack[THREAD_STACKSIZE_SMALL]; static char WORD_ALIGNED t2_stack[THREAD_STACKSIZE_SMALL]; static atomic_uint_least8_t is_running = ATOMIC_VAR_INIT(1); -void * t1_t2_func(void *arg) +void * t1_t2_malloc_func(void *arg) { (void)arg; while (atomic_load(&is_running)) { @@ -57,6 +57,30 @@ void * t1_t2_func(void *arg) return NULL; } +void * t1_t2_realloc_func(void *arg) +{ + (void)arg; + while (atomic_load(&is_running)) { + int *chunk = realloc(NULL, sizeof(int) * 1); + expect(chunk); + /* cppcheck-suppress memleakOnRealloc + * no need to free data on allocation failure, as expect() terminates then anyway */ + chunk = realloc(chunk, sizeof(int) * 2); + expect(chunk); + /* cppcheck-suppress memleakOnRealloc + * no need to free data on allocation failure, as expect() terminates then anyway */ + chunk = realloc(chunk, sizeof(int) * 4); + expect(chunk); + /* cppcheck-suppress memleakOnRealloc + * no need to free data on allocation failure, as expect() terminates then anyway */ + chunk = realloc(chunk, sizeof(int) * 8); + expect(chunk); + free(chunk); + } + + return NULL; +} + static void evil_schedule_hack_dont_do_this_at_home(uint8_t prio) { extern clist_node_t sched_runqueues[SCHED_PRIO_LEVELS]; @@ -66,6 +90,7 @@ static void evil_schedule_hack_dont_do_this_at_home(uint8_t prio) int main(void) { kernel_pid_t t1, t2; + int failed = 0; puts( "Test Application for multithreaded use of malloc()\n" "==================================================\n" @@ -78,41 +103,67 @@ int main(void) ); #ifndef NO_MALLINFO + /* in case the malloc implementation dynamically allocates management structures, + * do one malloc() / free() to obtain the baseline for mallinfo() + */ + free(malloc(sizeof(int))); struct mallinfo pre = mallinfo(); #else puts("WARNING: Use of mallinfo() disabled.\n"); #endif - t1 = thread_create(t1_stack, sizeof(t1_stack), THREAD_PRIORITY_MAIN + 1, - THREAD_CREATE_STACKTEST, t1_t2_func, NULL, "t1"); - t2 = thread_create(t2_stack, sizeof(t2_stack), THREAD_PRIORITY_MAIN + 1, - THREAD_CREATE_STACKTEST, t1_t2_func, NULL, "t2"); - expect((t1 != KERNEL_PID_UNDEF) && (t2 != KERNEL_PID_UNDEF)); + void *(* const funcs[])(void *) = { t1_t2_malloc_func, t1_t2_realloc_func }; + const char *tests[] = { "malloc()/free()", "realloc()/free()" }; - for (uint16_t i = 0; i < 2 * MS_PER_SEC; i++) { - xtimer_usleep(US_PER_MS); - /* shuffle t1 and t2 in their run queue. This should eventually hit - * during a call to malloc() or free() and disclose any missing - * guards */ - evil_schedule_hack_dont_do_this_at_home(THREAD_PRIORITY_MAIN + 1); - } + for (size_t i = 0; i < ARRAY_SIZE(funcs); i++) { + printf("Testing: %s\n", tests[i]); + t1 = thread_create(t1_stack, sizeof(t1_stack), THREAD_PRIORITY_MAIN + 1, + THREAD_CREATE_STACKTEST, funcs[i], NULL, "t1"); + t2 = thread_create(t2_stack, sizeof(t2_stack), THREAD_PRIORITY_MAIN + 1, + THREAD_CREATE_STACKTEST, funcs[i], NULL, "t2"); + expect((t1 != KERNEL_PID_UNDEF) && (t2 != KERNEL_PID_UNDEF)); - /* Don't keep threads spinning */ - atomic_store(&is_running, 0); - /* Give threads time to terminate */ - xtimer_usleep(10 * US_PER_MS); + for (uint16_t i = 0; i < 2 * MS_PER_SEC; i++) { + xtimer_usleep(US_PER_MS); + /* shuffle t1 and t2 in their run queue. This should eventually hit + * during a call to malloc() or free() and disclose any missing + * guards */ + evil_schedule_hack_dont_do_this_at_home(THREAD_PRIORITY_MAIN + 1); + } + + /* Don't keep threads spinning */ + atomic_store(&is_running, 0); + /* Give threads time to terminate */ + xtimer_usleep(10 * US_PER_MS); #ifndef NO_MALLINFO - struct mallinfo post = mallinfo(); + struct mallinfo post = mallinfo(); - /* RIOT's board or arch support hopefully doesn't use malloc, so there - * should be zero bytes allocated prior to the first call to malloc() in - * this test. But let's be forgiving and just expect that the number of - * allocated bytes before and after the test is equal. - */ - expect(pre.uordblks == post.uordblks); + /* RIOT's board or arch support hopefully doesn't use malloc, so there + * should be zero bytes allocated prior to the first call to malloc() in + * this test. But let's be forgiving and just expect that the number of + * allocated bytes before and after the test is equal. + */ + if (pre.uordblks != post.uordblks) { + failed = 1; + puts("Not all blocks were correctly freed!"); + printf("mallinfo().uordblks before test: %u, after test: %u\n", + (unsigned)pre.uordblks, (unsigned)post.uordblks); + } #endif - puts("TEST PASSED"); + } + + /* cppcheck-suppress knownConditionTrueFalse + * The actual test is that this application doesn't crash / hang due to memory corruptions. + * But if there is mallinfo() provided, we can also test for memory leaks in the malloc() + * implementation pretty much for free. Even if this implementation most likely comes from + * the standard C lib, it is still good to be at least aware of bugs in the used toolchain */ + if (failed) { + puts("TEST FAILED"); + } + else { + puts("TEST PASSED"); + } return 0; }