/*  malloc_debug.c: attempt at malloc/free replacement for debugging
    configure with --enable-debug to use it. Nowadays, valgrind
    will often be more useful.

    When configured with --enable-debug, and run with the DEBUG_MALLOC
    bit set this will log (in the debug log /tmp/rlwrap.debug) all
    malloc() and free() calls made by rlwrap (but not those made on
    behalf of rlwrap in library routines)

    At program exit this will log a list of all unfreed() memory
    blocks on the heap

    if DEBUG_WITH_TIMESTAMPS is set in debug  , the timestamp of those
    blocks (i.e. of their allocation) will be listed as well.
 

    
*/

/*  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 2 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; see the file COPYING.  If not, write to
    the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

    You may contact the author by:
    e-mail:  hanslub42@gmail.com

*/




#include "rlwrap.h"


#ifdef DEBUG
#  define USE_MALLOC_DEBUGGER
#  undef mymalloc
#  undef free
#endif

#ifdef USE_MALLOC_DEBUGGER



#define SLAVERY_SLOGAN "rlwrap is boss!"
#define FREEDOM_SLOGAN "free at last!"
#define SLOGAN_MAXLEN 20
#define TIMESTAMP_MAXLEN 30

extern int debug;

typedef void (*sighandler_t)(int);

typedef struct freed_stamp
{
  char magic[SLOGAN_MAXLEN];  /* magical string that tells us this something about this memory: malloced or freed? */
  char *file;                 /* source file where we were malloced/freed */ 
  int line;                   /* source line where we were malloced/freed */
  int size;
  char timestamp[TIMESTAMP_MAXLEN];
  void *previous;             /* maintain a linked list of malloced/freed memory for post-mortem investigations*/
} *Freed_stamp; 


static void* blocklist = 0;   /* start of linked list of allocated blocks. when freed, blocks stay on the list */

static int memory_usage = 0;

static char *offending_sourcefile;
static int offending_line;
sighandler_t old_segfault_handler;


/* local segfault handler, installed just before we dereference a pointer to test its writability */
static void
handle_segfault(int UNUSED(sig))
{
  fprintf(stderr, "free() called on bad (unallocated) memory at %s:%d\n", offending_sourcefile, offending_line);
  exit(1);
}


                      
/* allocates chunk of memory including a freed_stamp, in which we write
   line and file where we were alllocated, and a slogan to testify that
   we have been allocated and not yet freed. returns the address past the stamp
   If you think this memory will ever be freed by a normal free() use malloc_foreign instead
*/

void *
debug_malloc(size_t size,  char *file, int line)
{
  void *chunk;
  Freed_stamp stamp;

  if (!(debug & DEBUG_MEMORY_MANAGEMENT))
    return mymalloc(size);
  
  chunk = mymalloc(sizeof(struct freed_stamp) + size);
  stamp = (Freed_stamp) chunk;
  memory_usage += size;
  DPRINTF4(DEBUG_MEMORY_MANAGEMENT, "malloc size: %d at %s:%d: (total usage now: %d)",  (int) size, file, line, memory_usage); 
  strncpy(stamp->magic, SLAVERY_SLOGAN, SLOGAN_MAXLEN);
  stamp -> file = file;
  stamp -> line = line;
  stamp -> size = size;
  if (debug & DEBUG_WITH_TIMESTAMPS)
    timestamp(stamp -> timestamp, TIMESTAMP_MAXLEN);
  else
    stamp -> timestamp[0] = '\0';
  stamp -> previous = blocklist;
  blocklist = chunk;
  return (char *) chunk + sizeof(struct freed_stamp); 
}       



/* Verifies that ptr indeed points to memory allocaded by debug_malloc,
   and has not yet been freed. Doesn't really free it, but marks it as freed
   so that we easily notice double frees */
void
debug_free(void *ptr, char *file, int line)
{
  Freed_stamp stamp;

  if (!(debug & DEBUG_MEMORY_MANAGEMENT)) {
    free(ptr);
    return;
  }

  stamp = ((Freed_stamp) ptr) - 1;
  offending_sourcefile = file; /* use static variables to communicate with signal handler */
  offending_line = line;
  old_segfault_handler = signal(SIGSEGV, &handle_segfault);
  * (char *) ptr = 'x'; /* this, or the next statement  will provoke a segfault when address is not writable, i.e. in read-only memory or
                           not in a mamory-mapped area */
  if (strcmp(FREEDOM_SLOGAN, stamp -> magic) == 0) { /* Argghh! this memory has been freed before! */
    fprintf(stderr, "free() called twice, at %s:%d: (on memory already freed at %s:%d)\n",
            file, line, stamp->file, stamp->line);
    exit(1);
  } else if (strcmp(SLAVERY_SLOGAN, stamp -> magic) == 0) {
    DPRINTF4(DEBUG_MEMORY_MANAGEMENT, "free() (called at %s:%d) of memory malloced at %s:%d", file, line, stamp->file, stamp->line);
    strncpy(stamp->magic, FREEDOM_SLOGAN, SLOGAN_MAXLEN);
    stamp -> file = file;
    stamp -> line = line;
    memory_usage -= stamp -> size;
    signal(SIGSEGV, old_segfault_handler);
    /* don't really free ptr */
  } else {
    fprintf(stderr, "free() called (at %s:%d) on unmalloced memory <%s>, or memory not malloced by debug_malloc()\n",
            file, line, mangle_string_for_debug_log(ptr, 30));
    close_logfile();
    exit(1);
  }     
}       

/* this function calls free() directly, and should be used on memory that was malloc'ed outside our own jurisdiction,
   i.e. not by debug_malloc(); */
void free_foreign(void *ptr) {
  free(ptr); 
}

/* this function calls malloc() directly, and should be used on memory that could be freed  outside our own jurisdiction,
   i.e. not by debug_free(); */
void *malloc_foreign(size_t size) {
  return malloc(size);
}       

/* sometimes we put in one structure objects that were malloced elsewhere and our own mymalloced objects.
   we cannot free such a structure with free(), nor with free_foreign(). Solution: before using the "foreign" objects,
   copy them to mymalloced memory and free them immediately
   This function will be redefined to a NOP (i.e. just return its first argument) unless DEBUG is defined */
   
void *copy_and_free_for_malloc_debug(void *ptr, size_t size) {
  void *copy;
  if (ptr == NULL)
    return NULL;
  copy = debug_malloc(size,"foreign",0);
  memcpy(copy, ptr, size);
  free(ptr);
  return copy;
}


char *copy_and_free_string_for_malloc_debug(char* str) {
  if (str == NULL)
    return NULL;
  return copy_and_free_for_malloc_debug(str, strlen(str)+1);
}       


/* this function logs all non-freed memory blocks (in order to hunt for memory leaks) */
/* blocklist = NULL, hence it is a no-op unless DEBUG_MALLOC has been defined */
void debug_postmortem(void) {
  Freed_stamp p;
  char *block;
  DPRINTF0(DEBUG_MEMORY_MANAGEMENT,"Postmortem list of unfree memory blocks (most recently allocated first): ");
  for (p = (Freed_stamp) blocklist; p; p =  p ->previous) {
    if (strcmp(FREEDOM_SLOGAN, p -> magic) == 0)
      continue;
    else if (strcmp(SLAVERY_SLOGAN, p -> magic) == 0) {
      block = (char *) p + sizeof(struct freed_stamp);   
      DPRINTF5(DEBUG_MEMORY_MANAGEMENT, "%d bytes malloced at %s %s:%d, contents: <%s>", p->size, p -> timestamp, p ->file, p ->line, M(block));
    } else {    
      DPRINTF0(DEBUG_MEMORY_MANAGEMENT, "Hmmm,  unmalloced memory, or memory not malloced by debug_malloc()");
    }
  }     
}             


#endif /* def USE_MALLOC_DEBUGGER */
