/*
* 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);
}