From f23716fdcb99fc95c0b82a8624bad38cb23bfa62 Mon Sep 17 00:00:00 2001 From: Markus Minichmayr Date: Mon, 5 Mar 2018 16:59:07 +0100 Subject: Tests: Introducing custom memory allocation functions that allow testing for consistency and robustness of memory management. --- src/test/CMakeLists.txt | 2 + src/test/regression-utils.c | 14 ++++ src/test/regression.c | 7 ++ src/test/test-malloc.c | 180 ++++++++++++++++++++++++++++++++++++++++++++ src/test/test-malloc.h | 67 +++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 src/test/test-malloc.c create mode 100644 src/test/test-malloc.h (limited to 'src') diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 9e008c86..29001dba 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -96,6 +96,8 @@ set(regression_SRCS regression-utils.c regression-recur.c regression-storage.c + test-malloc.c + test-malloc.h ) if(WITH_CXX_BINDINGS) list(APPEND regression_SRCS regression-cxx.cpp) diff --git a/src/test/regression-utils.c b/src/test/regression-utils.c index 431cef85..8daa1797 100644 --- a/src/test/regression-utils.c +++ b/src/test/regression-utils.c @@ -23,6 +23,8 @@ #include "libical/ical.h" +#include "test-malloc.h" + #include int QUIET = 0; @@ -212,7 +214,19 @@ void test_run(const char *test_name, void (*test_fcn) (void), int do_test, int h test_header(test_name, test_set); if (!headeronly && (do_test == 0 || do_test == test_set)) { + testmalloc_reset(); (*test_fcn) (); + + /* TODO: Check for memory leaks here. We could do a check like the + following but we would have to implement the test-cases in a way + that all memory is freed at the end of each test. This would include + freeing built in and cached timezones. + + ok("no memory leaked", + (global_testmalloc_statistics.mem_allocated_current == 0) + && (global_testmalloc_statistics.blocks_allocated == 0)); + */ + if (!QUIET) printf("\n"); } diff --git a/src/test/regression.c b/src/test/regression.c index 3c344f26..be3a2a20 100644 --- a/src/test/regression.c +++ b/src/test/regression.c @@ -27,6 +27,7 @@ #include "regression.h" #include "libical/astime.h" +#include "test-malloc.h" #include "libical/ical.h" #include "libicalss/icalss.h" #include "libicalvcal/icalvcal.h" @@ -5205,6 +5206,12 @@ int main(int argc, char *argv[]) int do_header = 0; int failed_count = 0; + // We specify special versions of malloc et al. that perform some extra verifications. + // Most notably they ensure, that memory allocated with icalmemory_new_buffer() is freed + // using icalmemory_free() rather than using free() directly and vice versa. Failing to + // do so would cause the test to fail with assertions or access violations. + icalmemory_set_mem_alloc_funcs(&test_malloc, &test_realloc, &test_free); + set_zone_directory(TEST_ZONEDIR); icaltimezone_set_tzid_prefix(TESTS_TZID_PREFIX); diff --git a/src/test/test-malloc.c b/src/test/test-malloc.c new file mode 100644 index 00000000..49de3b3c --- /dev/null +++ b/src/test/test-malloc.c @@ -0,0 +1,180 @@ +/*====================================================================== +FILE: test-malloc.c + +(C) COPYRIGHT 2018-2022, Markus Minichmayr + + This library is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html + + Or: + + The Mozilla Public License Version 2.0. You may obtain a copy of + the License at https://www.mozilla.org/MPL/ +======================================================================*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "test-malloc.h" +#include "regression.h" + +#include +#include +#include + + + +struct testmalloc_statistics global_testmalloc_statistics; +static int global_testmalloc_remaining_attempts = -1; + +#define TESTMALLOC_MAGIC_NO 0x1234abcd +struct testmalloc_hdr { + uint32_t magic_no; + size_t size; +}; + +struct testmalloc_hdrlayout { + + struct testmalloc_hdr hdr; + int data; +}; + +#define TESTMALLOC_HDR_SIZE ((size_t) &((struct testmalloc_hdrlayout*) 0)->data) + + +void *test_malloc(size_t size) { + + void* block; + struct testmalloc_hdr* hdr; + + global_testmalloc_statistics.malloc_cnt++; + if (global_testmalloc_remaining_attempts == 0) { + global_testmalloc_statistics.malloc_failed_cnt++; + return NULL; + } + + block = malloc(size + TESTMALLOC_HDR_SIZE); + if (block == NULL) { + global_testmalloc_statistics.malloc_failed_cnt++; + return NULL; + } + + hdr = (struct testmalloc_hdr*) block; + hdr->magic_no = TESTMALLOC_MAGIC_NO; + hdr->size = size; + + global_testmalloc_statistics.mem_allocated_current += size; + if (global_testmalloc_statistics.mem_allocated_current > global_testmalloc_statistics.mem_allocated_max) { + global_testmalloc_statistics.mem_allocated_max = global_testmalloc_statistics.mem_allocated_current; + } + + global_testmalloc_statistics.blocks_allocated++; + + if (global_testmalloc_remaining_attempts > 0) { + global_testmalloc_remaining_attempts--; + } + + // cppcheck-suppress memleak + return (void*) &((struct testmalloc_hdrlayout *) hdr)->data; +} + +void *test_realloc(void* p, size_t size) { + + struct testmalloc_hdr* hdr; + size_t old_size; + + global_testmalloc_statistics.realloc_cnt++; + if (global_testmalloc_remaining_attempts == 0) { + global_testmalloc_statistics.realloc_failed_cnt++; + return NULL; + } + + if (p == NULL) { + global_testmalloc_statistics.realloc_failed_cnt++; + return NULL; + } + + hdr = (struct testmalloc_hdr *) (((uint8_t *) p) - TESTMALLOC_HDR_SIZE); + if (hdr->magic_no != TESTMALLOC_MAGIC_NO) { + global_testmalloc_statistics.realloc_failed_cnt++; + return NULL; + } + + old_size = hdr->size; + hdr->magic_no = 0; + + // cppcheck-suppress memleakOnRealloc; the mem block p passed to this function stays valid. + hdr = (struct testmalloc_hdr*) realloc(hdr, size + TESTMALLOC_HDR_SIZE); + if (hdr == NULL) { + global_testmalloc_statistics.realloc_failed_cnt++; + return NULL; + } + + hdr->magic_no = TESTMALLOC_MAGIC_NO; + hdr->size = size; + + global_testmalloc_statistics.mem_allocated_current += size - old_size; + if (global_testmalloc_statistics.mem_allocated_current > global_testmalloc_statistics.mem_allocated_max) { + global_testmalloc_statistics.mem_allocated_max = global_testmalloc_statistics.mem_allocated_current; + } + + if (global_testmalloc_remaining_attempts > 0) { + global_testmalloc_remaining_attempts--; + } + + return (void*) &((struct testmalloc_hdrlayout*)hdr)->data; +} + +void test_free(void* p) { + + struct testmalloc_hdr* hdr; + size_t old_size; + + if (p == NULL) { + return; + } + + global_testmalloc_statistics.free_cnt++; + + hdr = (struct testmalloc_hdr *) (((uint8_t *) p) - TESTMALLOC_HDR_SIZE); + + // The main objective of this check is to ensure, that only memory, that has been allocated via icalmemory is freed + // via icalmemory_free(). A side objective is to make sure, the block of memory hasn't been corrupted. + if (hdr->magic_no != TESTMALLOC_MAGIC_NO) { + + // If we end up here, then probably either of the following happened: + // * The calling code tries to free a block of memory via icalmemory_free() that has been allocated outside of + // icalmemory, e.g. via malloc(). + // * The header in front of the memory block being freed has been corrupted. + + ok("freed memory was allocated via icalmemory and has not been corrupted", hdr->magic_no == TESTMALLOC_MAGIC_NO); + assert(hdr->magic_no == TESTMALLOC_MAGIC_NO); + global_testmalloc_statistics.free_failed_cnt++; + return; + } + + old_size = hdr->size; + hdr->magic_no = 0; + + free(hdr); + + global_testmalloc_statistics.mem_allocated_current -= old_size; + global_testmalloc_statistics.blocks_allocated--; +} + + + +void testmalloc_reset() { + memset(&global_testmalloc_statistics, 0, sizeof(global_testmalloc_statistics)); + global_testmalloc_remaining_attempts = -1; +} + +/** Sets the maximum number of malloc or realloc attemts that will succeed. If +* the number is negative, no limit will be applied. */ +void testmalloc_set_max_successful_allocs(int n) { + global_testmalloc_remaining_attempts = n; +} diff --git a/src/test/test-malloc.h b/src/test/test-malloc.h new file mode 100644 index 00000000..f66d8703 --- /dev/null +++ b/src/test/test-malloc.h @@ -0,0 +1,67 @@ +/*====================================================================== +FILE: test-malloc.h + +(C) COPYRIGHT 2018-2022, Markus Minichmayr + + This library is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html + + Or: + + The Mozilla Public License Version 2.0. You may obtain a copy of + the License at https://www.mozilla.org/MPL/ +======================================================================*/ + +#ifndef TESTMALLOC_H +#define TESTMALLOC_H + +#include + + +struct testmalloc_statistics { + int malloc_cnt; + int realloc_cnt; + int free_cnt; + + int malloc_failed_cnt; + int realloc_failed_cnt; + int free_failed_cnt; + + size_t mem_allocated_max; + size_t mem_allocated_current; + int blocks_allocated; +}; + +extern struct testmalloc_statistics global_testmalloc_statistics; + +/** Allocates the specified amount of memory and returns a pointer to the allocated memory. + * Memory allocated using this function must be freed using test_free(). + * The number of allocations that can be made using this function can be limited via + * testmalloc_set_max_successful_allocs(). + */ +void *test_malloc(size_t size); + +/** Resizes the specified buffer. + * Can only be used with memory that has previously been allocated using test_malloc(). + */ +void *test_realloc(void* p, size_t size); + +/** Frees a block of memory that has previously been allocated via the test_malloc() function. Specifying memory that + * has not been allocated via test_malloc() causes an assertion. + */ +void test_free(void* p); + +/** Resets the memory management statistics and sets the number of successful + * allocations limit to infinite. + */ +void testmalloc_reset(); + +/** Sets the maximum number of malloc or realloc attemts that will succeed. If + * the number is negative, no limit will be applied. + */ +void testmalloc_set_max_successful_allocs(int n); + +#endif /* !TESTMALLOC_H */ \ No newline at end of file -- cgit v1.2.1