/*
 *      Copyright (C) 2001-2009 Claus-Justus Heine.

 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, 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.

 *      This file contains a simplistic malloc-debugger, intended to
 *      track memory leaks. The allocations are stored in round-robin
 *      list, including function-name, file-name, line-number. A
 *      memory leak reveals itself by accumulating orphaned memory
 *      blocks at the bottom of the list output by
 *      print_alloc_records(tail).
 *
 */

/* GNAH. Nothing more to be said ... */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#if defined(__MACH__)
  #include <stdlib.h>
#else
  #include <malloc.h>
  #include <stdlib.h>
#endif
#include <stdbool.h>

#define N_RECORDS 2000

#define REC_FREE 0

struct alloc_record {
  void *ptr;
  size_t size;
  unsigned long seq;
  const char *file;
  const char *func;
  int line;
};

static struct alloc_record records[N_RECORDS];

unsigned long malloc_seq;
int n_alloc;
size_t alloc_size;
int n_alloc_records;
int n_alloc_records_max;

static void record_alloc(void *ptr, size_t size,
			 const char *file, const char *func, int line)
{
  unsigned long seq_min;
  int i, seq_min_i = 0;
  bool overflow = true;

  for (seq_min = ~0, i = 0; i < N_RECORDS; i++) {
    if (records[i].seq == REC_FREE) {
      seq_min_i = i;
      overflow = false;
      break;
    } else if (records[i].seq < seq_min) {
      seq_min = records[i].seq;
      seq_min_i = i;
    }
  }
  if (overflow) {
    n_alloc_records = N_RECORDS;
  } else {
    ++n_alloc_records;
  }
  if (n_alloc_records > n_alloc_records_max) {
    n_alloc_records_max = n_alloc_records;
  }

  records[seq_min_i].seq  = ++malloc_seq;
  records[seq_min_i].ptr  = ptr;
  records[seq_min_i].size = size;
  records[seq_min_i].file = file;
  records[seq_min_i].func = func;
  records[seq_min_i].line = line;

  ++n_alloc;
  alloc_size += size;
}

static void record_free(void *ptr)
{
  int i;

  if (ptr == NULL) {
    return;
  }

  for (i = 0; i < N_RECORDS; i++) {
    if (ptr == records[i].ptr) {
      alloc_size -= records[i].size;
      memset(&records[i], 0, sizeof(records[i]));
      records[i].seq = REC_FREE;
      --n_alloc_records;
      break;
    }
  }
  --n_alloc;
}

void *malloc_record__(size_t size, const char *file, const char *func, int line)
{
  void *ptr;

  if (size == 0) {
    return NULL;
  }

  ptr = malloc(size);

  record_alloc(ptr, size, file, func, line);

  return ptr;
}

void free_record__(void *ptr)
{
  record_free(ptr);

  /* it is not an error if we do not find the ptr in our list; we only
   * store the last N_RECORDS allocations
   */
  __free_hook = NULL;
  free(ptr);
  __free_hook = (void (*)(void *, const void *))free_record__;
}

char *strdup_record__(const char *string,
		      const char *file, const char *func, int line)
{
  size_t len = strlen(string);
  char *newstring;
  
  newstring = malloc_record__(len+1, file, func, line);
  memcpy(newstring, string, len+1);

  return newstring;
}

void *realloc_record__(void *ptr, size_t size,
		       const char *file, const char *func, int line)
{
  record_free(ptr);
  ptr = realloc(ptr, size);
  record_alloc(ptr, size, file, func, line);

  return ptr;
}

void *calloc_record__(size_t nmemb, size_t size,
		      const char *file, const char *func, int line)
{
  void *ptr;
  size *= nmemb;

  ptr = malloc_record__(size, file, func, line);
  memset(ptr, 0, size);
  return ptr; 
}

static int seq_cmp(const void *_a, const void *_b)
{
  const struct alloc_record *a = _a, *b = _b;

  if (a->seq < b->seq) {
    return 1;
  }
  if (a->seq > b->seq) {
    return -1;
  }
  return 0;
}

void print_alloc_records(int tail)
{
  int i;

  if (tail < 0) {
    tail = N_RECORDS;
  }

  qsort(records, N_RECORDS, sizeof(struct alloc_record), seq_cmp);
  
  for (i = N_RECORDS - tail; i < N_RECORDS; i++) {
    if (records[i].seq == REC_FREE) {
      break;
    }
    fprintf(stderr, "%ld: %d@%p (%s, %s(), %d)\n",
	    records[i].seq,
	    (int)records[i].size,
	    records[i].ptr,
	    records[i].file,
	    records[i].func,
	    records[i].line);
  }
  fprintf(stderr, "#records: %d\n", i);
}

void zap_alloc_records(void)
{
  memset(records, 0, sizeof(records));
  malloc_seq = n_alloc = alloc_size = n_alloc_records = n_alloc_records_max = 0;
}

#if HAVE_MALLINFO
struct mallinfo _AI_mallinfo(void)
{
  return mallinfo();
}
#endif

#if __GNUC__

void malloc_debug_constructor(void) __attribute__((constructor));

void malloc_debug_constructor(void)
{
  __free_hook = (void (*)(void *, const void *))free_record__;
}

#endif

/*
 * Local Variables: ***
 * mode: c ***
 * c-basic-offset: 2 ***
 * End: ***
 */
