If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// lib_heap.c -- library heap management

#include "library.h"


/*
Provide a simple, portable means of checking for memory leaks and bounding
errors in the code.
*/
typedef struct memhdr_s {
    struct memhdr_s *prev;
    struct memhdr_s *next;
    int     bytes;
    byte    tag;

    byte    zone[3]; // catch bounding errors
} memhdr_t;

typedef struct {
    memhdr_t    *prev; // must match the beginning of memhdr_t
    memhdr_t    *next;
} memqueue_t;

memqueue_t mem_head = { (memhdr_t *)&mem_head, (memhdr_t *)&mem_head };


/*
==============
L_Malloc

Allocate the given amount of memory
==============
*/
void *L_Malloc(int bytes, int tag)
{
    memhdr_t *hdr;
    void *block;

    if (!bytes)
        return 0;

    hdr = (memhdr_t *)malloc(bytes + sizeof(memhdr_t) + 4);
    if (!hdr)
        throw LError("Out of memory");

    block = hdr + 1;

    hdr->prev = (memhdr_t *)&mem_head;
    hdr->next = mem_head.next;
    hdr->next->prev = hdr->prev->next = hdr;
    hdr->bytes = bytes;
    hdr->tag = tag;
    memset(hdr->zone, 0xCC, sizeof(hdr->zone));

    memset((byte *)block + bytes, 0xCC, 4);

    return block;
}


/*
==============
L_Strdup
==============
*/
char *L_Strdup(const char *string, int tag)
{
    int len;
    char *dest;

    len = strlen(string) + 1;
    dest = (char *)L_Malloc(len, tag);
    memcpy(dest, string, len);

    return dest;
}


/*
==============
L_Free

Free a block that was allocated by L_Malloc
==============
*/
void L_Free(void *block)
{
    memhdr_t *hdr = ((memhdr_t *)block) - 1;

    // check the protection zones
    if (memnchr(hdr->zone, 0xCC, sizeof(hdr->zone)) ||
        memnchr((byte *)block + hdr->bytes, 0xCC, 4))
        throw LError("Com_Free: Bounding error for block at %08x (%i bytes, tagged %i)",
                block, hdr->bytes, hdr->tag);

    // unlink
    hdr->prev->next = hdr->next;
    hdr->next->prev = hdr->prev;

    free(hdr);
}

/*
==============
L_Realloc

Reallocate the given memory block. Both block and bytes can be 0.
tag won't override the tag if the block already exists.
==============
*/
void *L_Realloc(void *block, int bytes, int tag)
{
    memhdr_t *hdr;

    if (!block)
        return L_Malloc(bytes, tag);

    hdr = ((memhdr_t *)block) - 1;

    // check the protection zones
    if (memnchr(hdr->zone, 0xCC, sizeof(hdr->zone)) ||
        memnchr((byte *)block + hdr->bytes, 0xCC, 4))
        throw LError("Com_Realloc: Bounding error for block at %08x (%i bytes, tagged %i)",
                block, hdr->bytes, hdr->tag);

    // if new size is 0, just free the block
    if (!bytes) {
        hdr->prev->next = hdr->next;
        hdr->next->prev = hdr->prev;

        free(hdr);
        return 0;
    }

    hdr = (memhdr_t *)realloc(hdr, sizeof(memhdr_t) + bytes + 4);
    block = hdr + 1;

    // re-link, memory might have been moved
    hdr->prev->next = hdr;
    hdr->next->prev = hdr;

    hdr->bytes = bytes;

    memset((byte *)block + bytes, 0xCC, 4);

    return block;
}

/*
==============
L_TagFree

Check for blocks with certain tags. Complain (and free) if any were found.
This should help to find memory leaks.
==============
*/
void L_TagFree(int start, int end)
{
    memhdr_t *next = (memhdr_t *)&mem_head;
    memhdr_t *hdr;
    bool leak = false;

    next = next->next;
    while(next != (memhdr_t *)&mem_head) {
        hdr = next;
        next = next->next; // whoaaa....

        if (start < 0 || (hdr->tag >= start && (end < 0 || hdr->tag <= end))) {
            L_Printf("Memory leak at %08x (%i bytes, tagged %i)\n",
                    hdr+1, hdr->bytes, hdr->tag);
            L_Free(hdr+1);
            leak = true;
        }
    }

    if (leak)
        throw LError("Memory leaks found!");
}