//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Memory management routines with debugging features.
//  Based on free code from SNIPPETS 9404 by Walter Bright.
//  ------------------------------------------------------------------

#include <climits>
#include <gdbgerr.h>
#include <gmemdbg.h>
#include <glog.h>
#include <gmemall.h>


//  ------------------------------------------------------------------
//  Global vars

int throw_alloc_extra = 0;

#if defined(GTHROW_LOG)
glog* throw_log = NULL;
#define TLOG if(throw_log) throw_log
#endif


//  ------------------------------------------------------------------


#define gmem_strdup strdup
#define gmem_malloc malloc
#define gmem_calloc calloc
#define gmem_realloc realloc
#define gmem_free free


//  ------------------------------------------------------------------

#if defined(GTHROW_DEBUG)


//  ------------------------------------------------------------------
//  Various values

const dword BEFOREVAL = 0x12345678L;
const dword AFTERVAL  = 0x87654321L;

const byte BADVAL    = 0xFF;
const byte MALLOCVAL = 0xEE;

const size_t BAD_SIZE    = 0xFFFFFFFF;
const size_t MALLOC_SIZE = 0xEEEEEEEE;


//  ------------------------------------------------------------------
//  Struct

#if defined(GOLD_CANPACK)
#pragma pack(1)
#endif

struct Throw {
  Throw* next;
  Throw* prev;
  const char* file;
  int    line;
  int    index;
  size_t nbytes;
  dword  beforeval;
  char   data[1];
};

#if defined(GOLD_CANPACK)
#pragma pack()
#endif

//  ------------------------------------------------------------------

struct ThrowIndex {
  Throw* pointer;
};


//  ------------------------------------------------------------------
//  Internal vars

int      throw_inited = 0;
int      throw_count;
uint32_t throw_allocations = 0;
Throw    throw_alloclist;
int      throw_max_count = 0;
Throw**  throw_index = NULL;
int      throw_index_size = 0;
int      throw_index_free = 0;
int      throw_last_free = -1;
uint32_t throw_index_cache_hits = 0;
int      throw_overhead = sizeof(Throw) - 1;

#define throw_index_init_size 1000
#define throw_index_increment 100


//  ------------------------------------------------------------------
//  Inlined functions

inline Throw* throw_ptrtodl(const void* ptr) { return (Throw*)((const char*)ptr-sizeof(Throw)+1); }
inline void* throw_dltoptr(Throw* dl) { return (void*)dl->data; }


//  ------------------------------------------------------------------

int throw_index_find_free() {

  if(throw_last_free != -1) {
    throw_index_cache_hits++;
    return throw_last_free;
  }
  else if(throw_index_free) {
    Throw** i = throw_index;
    for(int n=0; n<throw_index_size; n++,i++) {
      if(*i == NULL) {
        throw_last_free = n;
        return throw_last_free;
      }
    }
  }
  if(throw_index_size) {
    throw_index = (Throw**)realloc(throw_index, (throw_index_size+throw_index_increment)*sizeof(Throw*));
    throw_new(throw_index);
    memset(throw_index+throw_index_size, 0, throw_index_increment*sizeof(Throw*));
    throw_last_free = throw_index_size;
    throw_index_size += throw_index_increment;
    throw_index_free = throw_index_increment;
  }
  else {
    throw_index = (Throw**)calloc(throw_index_init_size, sizeof(Throw*));
    throw_new(throw_index);
    throw_index_size = throw_index_init_size;
    throw_index_free = throw_index_init_size;
    throw_last_free = 0;
  }
  return throw_last_free;
}


//  ------------------------------------------------------------------

int throw_index_add(Throw* pointer) {

  int index = throw_index_find_free();
  throw_index[index] = pointer;
  throw_last_free = -1;
  throw_index_free--;
  return index;
}


//  ------------------------------------------------------------------

void throw_index_remove(int index) {

  throw_index[index] = NULL;
  throw_last_free = index;
  throw_index_free++;
}


//  ------------------------------------------------------------------

Throw* throw_find_overrun(Throw* pointer) {

  Throw* last_candidate = NULL;
  long last_diff = LONG_MAX;
  Throw** i = throw_index;
  for(int n=0; n<throw_index_size; n++,i++) {
    long diff = (unsigned long)*i - (unsigned long)pointer;
    if((diff > 0) and (diff < last_diff)) {
      last_candidate = *i;
      last_diff = diff;
    }
  }
  return last_candidate;
}


//  ------------------------------------------------------------------

Throw* throw_find_underrun(Throw* pointer) {

  Throw* last_candidate = NULL;
  long last_diff = LONG_MAX;
  Throw** i = throw_index;
  for(int n=0; n<throw_index_size; n++,i++) {
    long diff = (unsigned long)pointer - (unsigned long)*i;
    if((diff > 0) and (diff < last_diff)) {
      last_candidate = *i;
      last_diff = diff;
    }
  }
  return last_candidate;
}


//  ------------------------------------------------------------------
//  Dump pointer information to the log

#if defined(GTHROW_LOG)

void throw_printdl(Throw* dl) {

  char buf[100];

  char* ptr = (char*)throw_dltoptr(dl);
  TLOG->printf(": Ptr (%p,%u) at [%s,%u].", ptr, (uint)dl->nbytes, CleanFilename(dl->file), (uint)dl->line);
  TLOG->printf(": %s", HexDump16(buf, ptr, dl->nbytes, HEX_DUMP2));

}

#else

inline void throw_printdl(Throw* dl) {}

#endif


//  ------------------------------------------------------------------
//  Debugging new

void throw_new_debug(const void* __ptr, const char* __file, int __line) {

  if(__ptr == NULL) {
    #if defined(GTHROW_LOG)
    TLOG->errmemory(__file, __line);
    TLOG->printf("! A memory allocation failed (out of memory).");
    TLOG->printf("+ Advice: Restart.");
    #endif
    MemoryErrorExit();
  }
}


//  ------------------------------------------------------------------
//  Debugging strdup()

char* throw_strdup_debug(const char* __str, const char* __file, int __line) {

  char* _ptr = (char*)(__str ? throw_calloc_debug(1, strlen(__str)+1, __file, __line) : NULL);
  return _ptr ? strcpy(_ptr, __str) : _ptr;
}


//  ------------------------------------------------------------------
//  Debugging malloc()

void* throw_malloc_debug(size_t __size, const char* __file, int __line) {

  void* _ptr = throw_calloc_debug(1, __size, __file, __line);
  if(_ptr)
    memset(_ptr, MALLOCVAL, __size);
  return _ptr;
}


//  ------------------------------------------------------------------
//  Debugging calloc()

void* throw_calloc_debug(size_t __items, size_t __size, const char* __file, int __line) {

  __size *= __items;
  __size += throw_alloc_extra;

  if(__size == 0) {
    #if defined(GTHROW_LOG)
    TLOG->errmemory(__file, __line);
    TLOG->printf("! Attempted to allocate zero bytes of memory.");
    TLOG->printf("+ Advice: This is a bug. Please report to the Author.");
    #endif
    MemoryErrorExit();
  }

  size_t _siz = sizeof(Throw) + __size + sizeof(AFTERVAL) - 1;

  Throw* dl = (Throw*)calloc(_siz, 1);
  if(dl == NULL) {
    #if defined(GTHROW_LOG)
    TLOG->errmemory(__file, __line);
    TLOG->printf("! A memory allocation failed (out of memory).");
    TLOG->printf(": Needed %u (%Xh) bytes.", (uint)__size, (uint)__size);
    if(__size == BAD_SIZE) {
      TLOG->printf("+ Info: Value could be from free'd data.");
      TLOG->printf("+ Info: This indicates a serious bug.");
      TLOG->printf("+ Advice: Report to the Author immediately.");
    }
    else if(__size == MALLOC_SIZE) {
      TLOG->printf("+ Info: Value could be from uninitialized data");
      TLOG->printf("+ Info: This indicates a serious bug.");
      TLOG->printf("+ Advice: Report to the Author immediately.");
    }
    else {
      TLOG->printf("+ Advice: Restart.");
    }
    #endif
    MemoryErrorExit();
  }

  dl->index = throw_index_add(dl);

  dl->file = __file;
  dl->line = __line;
  dl->nbytes = __size;
  dl->beforeval = BEFOREVAL;
  *(dword*)&(dl->data[__size]) = AFTERVAL;

  dl->next = throw_alloclist.next;
  dl->prev = &throw_alloclist;
  throw_alloclist.next = dl;
  if(dl->next != NULL)
    dl->next->prev = dl;

  throw_count++;
  throw_max_count = maximum_of_two(throw_count, throw_max_count);
  throw_allocations++;

  return throw_dltoptr(dl);
}


//  ------------------------------------------------------------------
//  Debugging realloc()

void* throw_realloc_debug(void* __oldptr, size_t __size, const char* __file, int __line) {

  void* _ptr;
  Throw* dl = throw_ptrtodl(__oldptr);

  if(__size == 0) {
    throw_free_debug(__oldptr,__file,__line);
    _ptr = NULL;
  }
  else if(__oldptr == NULL) {
    _ptr = throw_malloc_debug(__size,__file,__line);
  }
  else {
    _ptr = throw_malloc_debug(__size,__file,__line);
    if(dl->nbytes < __size)
      __size = dl->nbytes;
    memcpy(_ptr,__oldptr,__size);
    throw_free_debug(__oldptr,__file,__line);
  }
  return _ptr;
}


//  ------------------------------------------------------------------
//  Debugging free()

void throw_free_debug(void* __ptr, const char* __file, int __line) {

  Throw* dl;
  Throw* overrun_dl = NULL;
  Throw* underrun_dl = NULL;

  if(__ptr == NULL)
    return;
  int inerr = false;
  if(throw_count <= 0) {
    #if defined(GTHROW_LOG)
    TLOG->errmemory(__file, __line);
    TLOG->printf("! More free's than allocs.");
    TLOG->printf("+ Info: This indicates a potentially serious bug.");
    TLOG->printf("+ Advice: Report to the Author immediately.");
    #endif
    goto err;
  }
  dl = throw_ptrtodl(__ptr);
  if(dl->nbytes == BAD_SIZE) {
    #if defined(GTHROW_LOG)
    TLOG->errpointer(__file, __line);
    TLOG->printf("! A memory allocation was already free'd.");
    #endif
    goto err2;
  }
  if(dl->beforeval != BEFOREVAL) {
    #if defined(GTHROW_LOG)
    TLOG->errpointer(__file, __line);
    TLOG->printf("! An allocated memory region was underrun.");
    #endif
    dl->prev = dl->next = NULL;
    dl->file = NULL;
    underrun_dl = throw_find_underrun(dl);
    goto err2;
  }
  if(*(dword*)&dl->data[dl->nbytes] != AFTERVAL) {
    #if defined(GTHROW_LOG)
    TLOG->errpointer(__file, __line);
    TLOG->printf("! An allocated memory region was overrun.");
    #endif
    overrun_dl = throw_find_overrun(dl);
    inerr = true;
  }

  if(inerr)
    goto err2;

  if(dl->prev)
    dl->prev->next = dl->next;
  if(dl->next)
    dl->next->prev = dl->prev;

  throw_index_remove(dl->index);

  memset(dl,BADVAL,sizeof(*dl)+dl->nbytes);
  throw_count--;

  free(dl);
  return;

err2:
  throw_printdl(dl);
  if(underrun_dl) {
    #if defined(GTHROW_LOG)
    TLOG->printf("! Possibly caused by overrun in this allocation:");
    throw_printdl(underrun_dl);
    if(*(dword*)&underrun_dl->data[underrun_dl->nbytes] != AFTERVAL)
      TLOG->printf("! Overrun of previous allocation confirmed.");
    #endif
  }
  if(overrun_dl) {
    #if defined(GTHROW_LOG)
    TLOG->printf("! Possibly cause - Underrun in this allocation:");
    throw_printdl(overrun_dl);
    if(overrun_dl->beforeval != BEFOREVAL)
      TLOG->printf("! Underrun of previous allocation confirmed.");
    #endif
  }
  #if defined(GTHROW_LOG)
  TLOG->printf(": Detected while freeing the allocation.");
  TLOG->printf("+ Info: This indicates a serious bug.");
  TLOG->printf("+ Advice: Report to the Author immediately.");
  #endif
err:
  //PointerErrorExit();
  ;
}


//  ------------------------------------------------------------------
//  Check all allocations

void throw_check_debug(const char* __file, int __line) {

  Throw* dl = throw_alloclist.next;
  while(dl != NULL) {
    throw_checkptr_debug(throw_dltoptr(dl), __file, __line);
    dl = dl->next;
  }
}


//  ------------------------------------------------------------------
//  Check individual allocation

void throw_checkptr_debug(const void* __ptr, const char* __file, int __line) {

  int inerr = false;
  Throw* dl;
  Throw* overrun_dl = NULL;
  Throw* underrun_dl = NULL;
  if(__ptr == NULL) {
    #if defined(GTHROW_LOG)
    TLOG->errpointer(__file, __line);
    TLOG->printf("! Found NULL pointer instead of allocated memory.");
    #endif
    goto err3;
  }
  dl = throw_ptrtodl(__ptr);
  if(dl->beforeval != BEFOREVAL) {
    #if defined(GTHROW_LOG)
    TLOG->errpointer(__file, __line);
    TLOG->printf("! An allocated memory region was underrun.");
    #endif
    dl->prev = dl->next = NULL;
    dl->file = NULL;
    underrun_dl = throw_find_underrun(dl);
    goto err2;
  }
  if(*(dword*)&dl->data[dl->nbytes] != AFTERVAL) {
    #if defined(GTHROW_LOG)
    TLOG->errpointer(__file, __line);
    TLOG->printf("! An allocated memory region was overrun.");
    #endif
    overrun_dl = throw_find_overrun(dl);
    inerr = true;
  }
  if(inerr)
    goto err2;
  return;

err2:
  throw_printdl(dl);
  if(underrun_dl) {
    #if defined(GTHROW_LOG)
    TLOG->printf("! Possibly caused by overrun in this allocation:");
    throw_printdl(underrun_dl);
    if(*(dword*)&underrun_dl->data[underrun_dl->nbytes] != AFTERVAL)
      TLOG->printf("! Overrun of previous allocation confirmed.");
    #endif
  }
  if(overrun_dl) {
    #if defined(GTHROW_LOG)
    TLOG->printf("! Possibly cause - Underrun in this allocation:");
    throw_printdl(overrun_dl);
    if(overrun_dl->beforeval != BEFOREVAL)
      TLOG->printf("! Underrun of previous allocation confirmed.");
    #endif
  }
err3:
  #if defined(GTHROW_LOG)
  TLOG->printf(": Detected while checking the allocation.");
  TLOG->printf("+ Info: This indicates a serious bug.");
  TLOG->printf("+ Advice: Report to the Author immediately.");
  #endif
  PointerErrorExit();
}


//  ------------------------------------------------------------------
//  Termination function

static void throw_term(void) {

  if(throw_inited) {
    #if defined(GTHROW_LOG)
    if(throw_count)
      TLOG->printf("! Detected %i unfree'd memory allocation%s.", throw_count, throw_count==1?"":"s");
    if(not error_exit) {
      Throw* dl = throw_alloclist.next;
      int count = throw_count;
      for(; dl; ) {
        Throw* dl_next = dl->next;
        throw_printdl(dl);
        throw_free_debug(throw_dltoptr(dl), __FILE__, __LINE__);
        dl = dl_next;
      }
      throw_count = count;
      if(throw_count) {
        TLOG->printf("+ Info: The memory should have been free'd before exit.");
        TLOG->printf("+ Info: This indicates a potentially serious bug.");
        TLOG->printf("+ Advice: Report to the Author immediately.");
      }
    }
    #endif
    free(throw_index);
    throw_inited = 0;
  }
}


//  ------------------------------------------------------------------
//  Init function

void throw_init() {

  if(throw_inited == 0) {
    throw_count = 0;
    throw_alloclist.next = NULL;
    throw_alloclist.prev = NULL;
    throw_alloclist.file = __FILE__;
    throw_alloclist.line = __LINE__;
    throw_alloclist.nbytes = 0;
    throw_alloclist.beforeval = BEFOREVAL;
    throw_alloclist.data[0] = 0xFF;
    throw_inited++;
    atexit(throw_term);
  }
}

//  ------------------------------------------------------------------

#ifdef gmem_strdup
#undef gmem_strdup
#define gmem_strdup(a) throw_strdup_debug(a, file, line)
#undef gmem_malloc
#define gmem_malloc(a) throw_malloc_debug(a, file, line)
#undef gmem_calloc
#define gmem_calloc(a,b) throw_calloc_debug(a, b, file, line)
#undef gmem_realloc
#define gmem_realloc(a,b) throw_realloc_debug(a, b, file, line)
#undef gmem_free
#define gmem_free(a) throw_free_debug(a, file, line)
#endif

#endif


//  ------------------------------------------------------------------

void* throw_outofmem_report(const char* file, int line, uint size) {

  #if defined(GTHROW_LOG)
  TLOG->errmemory(file, line);
  if(size == 0) {
    TLOG->printf("! Attempted to allocate zero bytes of memory.");
    TLOG->printf("+ Advice: This is a bug. Please report to the Author.");
  }
  else {
    TLOG->printf("! A memory allocation failed (out of memory).");
    TLOG->printf(": Needed %u (%Xh) bytes.", size, size);
    TLOG->printf("+ Advice: Restart.");
  }
  #else
  NW(file);
  NW(line);
  NW(size);
  #endif
  MemoryErrorExit();
  return NULL;
}


//  ------------------------------------------------------------------

void throw_xnew_debug(void* ptr, const char* file, int line) {

  if(ptr == NULL)
    throw_outofmem_report(file, line, 0);
}


//  ------------------------------------------------------------------

char* throw_xstrdup_debug(const char* str, const char* file, int line) {

  char* s = gmem_strdup(str);
  return s ? s : (char*)throw_outofmem_report(file, line, strlen(str));
}


//  ------------------------------------------------------------------

void* throw_xmalloc_debug(size_t size, const char* file, int line) {

  void* p = gmem_malloc(size+throw_alloc_extra);
  return p ? p : throw_outofmem_report(file, line, size);
}


//  ------------------------------------------------------------------

void* throw_xcalloc_debug(size_t items, size_t size, const char* file, int line) {

  void* p = gmem_calloc(items, size+throw_alloc_extra);
  return p ? p : throw_outofmem_report(file, line, items*size);
}


//  ------------------------------------------------------------------

void* throw_xrealloc_debug(void* ptr, size_t size, const char* file, int line) {

  void* p = gmem_realloc(ptr, size+throw_alloc_extra);
  return p ? p : throw_outofmem_report(file, line, size);
}


//  ------------------------------------------------------------------

void throw_xfree_debug(void* ptr, const char* file, int line) {

  if(ptr)
    gmem_free(ptr);
}


//  ------------------------------------------------------------------
