/* * Allocation wrapper for test purposes. * * Copyright © 2024 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #ifndef TEST_MALLOC_HOOK # define TEST_MALLOC_HOOK 1 #endif #include "cdecl-internal.h" #undef malloc static union test_alloc { union test_alloc *p; max_align_t a; size_t v; } *alloc_head; enum { ALLOC_NEXT_PTR, ALLOC_STATE, ALLOC_SIZE, ALLOC_PAYLOAD }; #define ALLOC_STATE_ALLOCATED 0x7143u #define ALLOC_STATE_FREED 0xdeadu /* * Hook for testing allocation behaviour of the library, enabling a basic * verification in test cases that the library does not leak memory. * * The library sources must be recompiled with -DTEST_MALLOC_HOOK to * make use of this functionality. * * In this implementation, allocated memory is never freed, instead just * marked with an indication that the memory is no longer live. */ void *test_realloc_hook(void *p, size_t n) { union test_alloc *old_alloc = p, *alloc; if (old_alloc) { old_alloc = &old_alloc[-ALLOC_PAYLOAD]; if (old_alloc[ALLOC_STATE].v != ALLOC_STATE_ALLOCATED) { printf("Bail out! %p is not a live allocation!\n", p); exit(99); } old_alloc[ALLOC_STATE].v = ALLOC_STATE_FREED; printf("# %p freed\n", p); } if (!n) return NULL; n = (n + sizeof *alloc - 1) / sizeof *alloc; alloc = malloc((n + ALLOC_PAYLOAD) * sizeof *alloc); if (!alloc) return NULL; alloc[ALLOC_NEXT_PTR].p = alloc_head; alloc_head = alloc; alloc[ALLOC_STATE].v = ALLOC_STATE_ALLOCATED; alloc[ALLOC_SIZE].v = n * sizeof *alloc; if (old_alloc) { n = old_alloc[ALLOC_SIZE].v; memcpy(&alloc[ALLOC_PAYLOAD], &old_alloc[ALLOC_PAYLOAD], n); } p = &alloc[ALLOC_PAYLOAD]; printf("# %p allocated\n", p); return p; } /* * Returns the total number of allocations that have not yet been freed. */ size_t test_live_allocations(void) { union test_alloc *a; size_t ret = 0; for (a = alloc_head; a; a = a[ALLOC_NEXT_PTR].p) { void *p = &a[ALLOC_PAYLOAD]; switch (a[ALLOC_STATE].v) { case ALLOC_STATE_ALLOCATED: printf("# %p still live\n", p); ret++; break; case ALLOC_STATE_FREED: break; default: printf("Bail out! detected %p state corruption\n", p); exit(99); } } return ret; } /* * Function called from output.c but not needed for error messaging * (only current user of this file) */ const char *cdecl__token_name(unsigned token) { printf("Bail out! stub cdecl__token_name called\n"); exit(99); }