/* Leak.c - Functions for debugging memory allocation...
 * Written by Ron S. Novy <ron.novy@yahoo.com>
 *
 *   In order for the leak test to work you must include leak.h in each source
 * file you wish to test and define DEBUG_LEAKS (preferably on the command line
 * or in your IDE) and compile all source that includes leak.h header file.
 * leak.h must be included before any code but after all other include files in
 * order to work properly.
 *
 *   Another thing to note is that if you end the program with anything other
 * then the 'exit' function then the memory leak list may not be written or
 * your program might crash (MinGW). This is because the file is closed by the
 * CRT code before the atexit handler (_debug_atexit) is called or the function
 * is not called at all.  So be sure to end with 'exit(x)' where appropriate
 * instead of 'abort' or a 'return 0' in main...
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <io.h>

#define _BUF_SIZE_ 4096

#include "ucode\dll.h"
#include "ucode\unicode.h"

#define INCLUDE_FIFO
#include "ucode/cbuf.h"

#define OMF_DEBUG_FILE_NAME "omfdebug.txt"

/* atexit function setup */
static FILE *debug_file = NULL;
static void _debug_atexit(void);

#ifdef DEBUG
static void init_debug_file(void)
{
    if (!debug_file) {
        //atexit(_debug_atexit);// Not needed here... We'll just call it on our own.
        debug_file = fopen(OMF_DEBUG_FILE_NAME, "wb");
    }
}
#endif // DEBUG



#ifdef DEBUG_LEAKS

#include <time.h>


/* Define REMOVE_ON_FREE to remove data from the list once it has freed.
 * This is recommended as it can really slow things down otherwise...
 */
#define REMOVE_ON_FREE

typedef enum LEAK_e {
    mem_leak = 0,
    file_leak,
    ffile_leak
} LEAK_e;


typedef struct MEM_LL {
    char    *source_file;
    int      source_line;
    LEAK_e   type;
    size_t   bytes;
    void    *pointer;
    char    *fname;
    int      free;        /* TRUE if 'free' was called on this pointer */

    struct MEM_LL *prev;
    struct MEM_LL *next;
} MEM_LL;


/* Global list */
static MEM_LL *head = NULL;
static MEM_LL *tail = NULL;



/* Locate and return a pointer to the MEM_LL object associated with 'ptr'. */
static MEM_LL *uc_find_mm(void *ptr)
{
    MEM_LL *i;

    if (!head || !ptr)
        return NULL;

    for (i = head; i; i = i->next) {
        if (i->pointer == ptr) {
            return i;
        }
    }
    return NULL;
}



/* Create a new blank MEM_LL object and add it to the global list */
static MEM_LL *uc_new_mm(void)
{
    MEM_LL *m = malloc(sizeof(MEM_LL));
    init_debug_file();
    if (!m)
        return NULL;

    /* Clear it and add to global list */
    memset(m, 0, sizeof(MEM_LL));
    if (!head) {
        head = tail = m;
    }
    else {
        tail->next = m;
        m->prev = tail;
        tail = m;
    }
    return m;
}



#ifdef REMOVE_ON_FREE
/* Delete a single MEM_LL and re-link the next and previous in the list */
static void uc_delete_mm(MEM_LL *mm)
{
    MEM_LL *p, *n;

    if (mm) {
        p = mm->prev;
        n = mm->next;

#ifdef FREE_LEAKS
        if (!mm->free) {
            switch (mm->type) {
                case mem_leak:
                    free(mm->pointer);
                    break;
                case file_leak:
                    close((int)mm->pointer);
                    break;
                case ffile_leak:
                    fclose((FILE*)mm->pointer);
                    break;
            }
        }
#endif /* ifdef FREE_LEAKS */

        free(mm->source_file);
        free(mm->fname);
        free(mm);

        if (p)
            p->next = n;
        else
            head = n;

        if (n)
            n->prev = p;

        tail = head;
        while (tail && tail->next) tail = tail->next;
    }
}
#endif /* ifdef REMOVE_ON_FREE */



/* Destroy a whole list of MEM_LL objects */
static void uc_destroy_mm(MEM_LL *mm)
{
    if (mm) {
        if (mm->prev)
            mm->prev->next = NULL;

        uc_destroy_mm(mm->next);
        mm->next = NULL;

#ifdef FREE_LEAKS
        if (!mm->free) {
            switch (mm->type) {
                case mem_leak:
                    debug_printf(
                        "forcing memory free:0x%08x,%d\t\t%d:%s\n",
                        (int)mm->pointer, mm->bytes,
                        mm->source_line, mm->source_file);
                    free(mm->pointer);
                    break;
                case file_leak:
                    debug_printf(
                        "forcing file closed: %s, %d:%s\n",
                        (mm->fname)?mm->fname:"(null)",
                        mm->source_line, mm->source_file);
                    close((int)mm->pointer);
                    break;
                case ffile_leak:
                    debug_printf(
                        "forcing file closed: %s, %d:%s\n",
                        (mm->fname)?mm->fname:"(null)",
                        mm->source_line, mm->source_file);
                    fclose((FILE*)mm->pointer);
                    break;
            }
        }
#endif /* ifdef FREE_LEAKS */

        free(mm->source_file);
        free(mm->fname);
        free(mm);
    }
}



/* uc_mem_malloc:
 *   Our garbage collector.
 */
DLL_EXPORT void *uc_mem_malloc(size_t bytes, const char *file, int line)
{
    void *ret = malloc(bytes);
    MEM_LL *m;

    if (!ret)
        return NULL;

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        free(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->type = mem_leak;
    m->bytes = bytes;
    m->pointer = ret;
    m->fname = NULL;
    m->free = FALSE;

    return ret;
}



/* uc_mem_zmalloc:
 *   Our garbage collector.
 */
DLL_EXPORT void *uc_mem_zmalloc(size_t bytes, const char *file, int line)
{
    void *ret = zmalloc(bytes);
    MEM_LL *m;

    if (!ret)
        return NULL;

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        free(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->type = mem_leak;
    m->bytes = bytes;
    m->pointer = ret;
    m->fname = NULL;
    m->free = FALSE;

    return ret;
}



/* uc_mem_free:
 *  Free the memory and mark it as free in our list.
 */
DLL_EXPORT void uc_mem_free(void *p)
{
    MEM_LL *i;

    if (!p)
        return;

    i = uc_find_mm(p);
    if (i) {
        i->pointer = NULL;
        i->free = TRUE;
    }

    free(p);

#ifdef REMOVE_ON_FREE
    uc_delete_mm(i);
#endif
}



/* uc_mem_realloc:
 *  Catch realloc function...
 */
DLL_EXPORT void *uc_mem_realloc(void *ptr, size_t size, const char *file, int line)
{
    void *ret;
    MEM_LL *i = uc_find_mm(ptr);

    ret = realloc(ptr, size);

    /* Allocate new entry for linked list if needed */
    if (!i) {
        i = uc_new_mm();
        if (!i) {
            free(ret);
            return NULL;
        }

        /* Fill in the data */
        i->free = FALSE;
    }


    /* Update the rest of the information */
    free(i->source_file);
    i->source_file = strdup(file);
    i->source_line = line;
    i->bytes = size;
    i->pointer = ret;
    i->fname = NULL;
    if (ret)
        i->free = FALSE;
    else
        i->free = TRUE;

    return ret;
}



/* uc_mem_calloc:
 *  Catch the calloc function and use our own...
 */
DLL_EXPORT void *uc_mem_calloc(size_t num, size_t size, const char *file, int line)
{
    return uc_mem_malloc(num * size, file, line);
}



/* uc_mem_strdup:
 *  Catch the strdup function and use our own...
 */
DLL_EXPORT char *uc_mem_strdup(const char *str, const char *file, int line)
{
    void *ret = strdup(str);
    MEM_LL *m;

    if (!ret)
        return NULL;

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        free(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->bytes = strlen(str) + 1;
    m->pointer = ret;
    m->fname = NULL;
    m->free = FALSE;

    return ret;
}



/* uc_mem_ustrdup:
 *  Catch the strdup function and use our own...
 */
DLL_EXPORT char *uc_mem_ustrdup(const char *str, const char *file, int line)
{
    void *ret = ustrdup(str);
    MEM_LL *m;

    if (!ret)
        return NULL;

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        free(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->bytes = ustrlen(str) + ucwidth(0);
    m->pointer = ret;
    m->fname = NULL;
    m->free = FALSE;

    return ret;
}



#ifdef INCLUDE_FIFO
/* uc_leak_fifo_:
 *   Leak information for FIFO buffers.
 */
DLL_EXPORT FIFO *uc_leak_fifo_open(const char *name, const char *mode, const char *file, int line)
{
    MEM_LL *m;
    FIFO *ret = fifo_open(name, mode);

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        fifo_destroy(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->type = ffile_leak;
    m->bytes = sizeof(FIFO); /* Plus the buffer itself, but don't include that */
    m->pointer = ret;       /* We're tracking FIFO */
    m->fname = NULL;
    m->fname = strdup("fifo_open");
    m->free = FALSE;
    return ret;
}



DLL_EXPORT FIFO *uc_leak_fifo_create(int block_size, const char *file, int line)
{
    MEM_LL *m;
    FIFO *ret = fifo_create(block_size);

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        fifo_destroy(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->type = ffile_leak;
    m->bytes = sizeof(FIFO); /* Plus the buffer itself, but don't include that */
    m->pointer = ret;       /* We're tracking FIFO */
    m->fname = strdup("fifo_create");
    m->free = FALSE;
    return ret;
}



DLL_EXPORT void uc_leak_fifo_destroy(FIFO *f, const char *file, int line)
{
    MEM_LL *i;

    if (!f)
        return;

    i = uc_find_mm(f);
    if (i) {
        i->pointer = NULL;
        i->free = TRUE;
    }

    fifo_destroy(f);

#ifdef REMOVE_ON_FREE
    uc_delete_mm(i);
#endif
}



DLL_EXPORT void *uc_leak_fifo_close(FIFO *f, const char *file, int line)
{
    void *ret = NULL;
    uint bytes;
    MEM_LL *i, *m;

    if (!f)
        return NULL;

    i = uc_find_mm(f);
    if (i) {
        i->pointer = NULL;
        i->free = TRUE;
    }

    bytes = fifo_size(f);
    ret = fifo_close(f);
    uc_delete_mm(i);

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        free(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->type = mem_leak;
    m->bytes = bytes;
    m->pointer = ret;
    m->fname = NULL;
    m->free = FALSE;

    return ret;
}
#endif /* ifdef INCLUDE_FIFO */



/* uc_leak_fopen:
 *   Collect leak information on open files.
 */
DLL_EXPORT FILE *uc_leak_fopen(
    const char *file, int line, const char *filename, const char *mode)
{
    FILE *ret = fopen(filename, mode);
    MEM_LL *m;

    if (!ret)
        return NULL;

    /* Allocate new entry for linked list */
    m = uc_new_mm();
    if (!m) {
        free(ret);
        return NULL;
    }

    /* Fill in the data */
    m->source_file = strdup(file);
    m->source_line = line;
    m->type = ffile_leak;
    m->bytes = sizeof(FILE);
    m->pointer = (void*)ret;
    m->fname = strdup(filename);
    m->free = FALSE;

    return ret;
}



/* uc_leak_fclose:
 *  Close the file and mark it as free in our list.
 */
DLL_EXPORT void uc_leak_fclose(FILE *p)
{
    MEM_LL *i;

    if (!p)
        return;

    i = uc_find_mm(p);
    if (i) {
        i->pointer = NULL;
        i->free = TRUE;
    }

    fclose(p);

#ifdef REMOVE_ON_FREE
    uc_delete_mm(i);
#endif
}



static void print40(int8 *buffer, uint32 length)
{
    uint32 i;
    uint32 temp;

    /* Length includes terminating null for strings. */
    debug_printf("\tDATA(40): ");
    for (i = 0; i < MIN(length, 40); i++ ) {
        uchar ch = buffer[i];

        if (ch < 32 || ch >= 0x80) {
            if (i != length - 1)
                debug_printf("~~ ");
        }
        else {
            debug_printf("%c", ch);
        }
    }
    debug_printf("\n");

    debug_printf("\tRAWDATA(12): ");
    temp = 0;
    for (i = 0; i < MIN(length, 12); i++) {
        uchar ch = buffer[i];
        debug_printf("%02x ", ch);
    }
    debug_printf("\n");
}



/* uc_mem_dump:
 *  Dumps all the information to a log file and will free any memory
 * still allocated.
 *
 * Note: We use fprintf with our own debug file because the file debug_printf
 * uses will be closed...
 */
static void _uc_mem_dump(const char *file, int line)
{
    MEM_LL *i;
    uint32 leaks = 0;
    time_t t = time(NULL);

    debug_printf(
        "\n"
        "\n"
        "uc_mem_dump: Called from line %d of %s\n"
        "on %.24s, Dump:\n\n", line, file, ctime(&t));

    for (i = head; i; i = i->next) {
        switch (i->type) {
            case file_leak:
            case ffile_leak:
                debug_printf(
                    "0x%08x:%d\t\t%s is %s, %d:%s\n",
                    (uint)i->pointer, i->bytes, (i->fname)?i->fname:"(null)",
                    (i->free)?"closed":" OPEN!", i->source_line, i->source_file);
                break;
            case mem_leak:
            default:
                debug_printf(
                    "0x%08x:%d\t\t%s, %d:%s\n",
                    (uint)i->pointer, i->bytes, (i->free)?"  free":" LEAK!",
                    i->source_line, i->source_file);
                print40((int8*)i->pointer, i->bytes);
                break;
        }
        if (!i->free)
            leaks += i->bytes;
    }

    debug_printf(
        "\n%d bytes leaked.\n\n", leaks);

    uc_destroy_mm(head);
    head = tail = NULL;
}
#endif /* ifdef DEBUG_LEAKS */



/* ************************************ *
 *  Debug printf and fprintf functions  *
 * ************************************ */
static void _debug_atexit(void)
{
#ifdef DEBUG_LEAKS
    /* Note: If the program exits without calling 'exit' then the file can be
     * closed before we get here. Just be sure to exit with the 'exit' function.
     * In the case you don't follow those directions we will try to close and
     * re-open the file in append mode before writing the list.  No gaurantees
     * that it won't crash though...
     * Found out this happens when the dll uses a static c runtime or the dll
     * and applications use different c runtimes...
     *
    if (debug_file) {
        fflush(debug_file);
        fclose(debug_file);
    }
    debug_file = fopen(OMF_DEBUG_FILE_NAME, "ab");*/

    _uc_mem_dump(__FILE__, __LINE__);
#endif /* ifdef DEBUG_LEAKS */

    if (debug_file) {
        fflush(debug_file);
        fclose(debug_file);
        debug_file = NULL;
    }
}


DLL_EXPORT void debug_atexit(void)
{
    _debug_atexit();
}


/* debug_printf:
 *  When printing from a DLL we print only to our debug file.
 */
int debug_printf(const char *format, ...)
{
    char buf[_BUF_SIZE_];
    int ret;
    va_list ap;

    va_start(ap, format);
    ret = uvszprintf(buf, sizeof(buf), format, ap);
    va_end(ap);


#ifndef BUILD_DLL
    {
        int i;
        for (i = 0; buf[i]; i++)
            putchar(buf[i]);
        fflush(stdout);
    }
#endif

#ifdef DEBUG
    init_debug_file();

    if (debug_file) {
        int i;
        for (i = 0; buf[i]; i++) {
            if (buf[i] == '\n') {
                fputc('\r', debug_file);
                fputc('\n', debug_file);
            }
            else
                fputc(buf[i], debug_file);
        }
        //fwrite(buf, 1, strlen(buf), debug_file);
        fflush(debug_file);
    }
#endif // DEBUG

    return ret;
}



int debug_fprintf(FILE *f, const char *format, ...)
{
    char buf[_BUF_SIZE_];
    int ret;
    va_list ap;

    va_start(ap, format);
    ret = uvszprintf(buf, sizeof(buf), format, ap);
    va_end(ap);

    fwrite(buf, 1, ret, f);
    fflush(f);

#ifdef DEBUG
    init_debug_file();

    if (debug_file) {
#ifdef _WIN32
        int i;
        for (i = 0; buf[i]; i++) {
            if (buf[i] == '\n') {
                fputc('\r', debug_file);
                fputc('\n', debug_file);
            }
            else
                fputc(buf[i], debug_file);
        }
#else
        fwrite(buf, 1, strlen(buf), debug_file);
#endif
        fflush(debug_file);
    }
#endif // DEBUG

    return ret;
}

