/* Smartalloc.c       Copyright Clinton Staley 1991
 * modified by ceh 1992 to include calloc())
 * modified by bkm 1992 to include realloc() and strdup()
 *
 * Rewritten April 10, 1992 by Steve Mead (smead) to use a hash table
 * instead of a linked list.  Also changed for loops to memset's and memcpy's.
 * Speed improvements are phenomenal.
 */

#undef MDEBUG

#include <stdio.h>
#ifdef MACOSX
#include <stdlib.h>
#endif
#ifndef MACOSX
#include <malloc.h>
#endif
#include <string.h>
#include <assert.h>
#include "salloc.h"
#include "table.h"
#include "str_table.h"

#undef report_offenders

#define BOUND 0xC
#define FREED 0xB
#define BOUND_BYTES 8

#ifndef YES
#define YES 1
#endif

#ifndef NO
#define NO 0
#endif

typedef struct track_t {
   void *data;
   int space;
   char *file;
   int line;
} track_t;

static table_t table = NULL;
static long malloc_count = 0;
static long free_count = 0;

void _free(void *data)
{
   free(data);
}

typedef struct line_stats_t {
   int line;
   int size;
   int number;
   struct line_stats_t *next;
} line_stats_t;

typedef struct file_stats_t {
   char *file;
   line_stats_t *lines;
   long size;
   long number;
   long num_lines;
} file_stats_t;

int sort_files(void *f1, void *f2)
{
   return ((file_stats_t*)f2)->size - ((file_stats_t*)f1)->size;
}

int sort_lines(void *c1, void *c2)
{
   return ((line_stats_t*)c2)->size - 
          ((line_stats_t*)c1)->size;
}

void output_offenders(file_stats_t *stats, int num_stats, long min)
{
   int cnt, line_cnt;
   line_stats_t *next, *temp;

   for (cnt=0; cnt < num_stats; stats++, cnt++) {
      line_stats_t *lines, *l;

      fprintf(stderr, "File: %s - %ld total bytes in %ld allocations.\n", 
       stats->file, stats->size, stats->number);
      l = lines = (line_stats_t*)malloc(sizeof(line_stats_t)*stats-> num_lines);
      for (temp = stats->lines; (next=temp) != NULL; ) {
         temp = next->next;
         *l++ = *next;
         free(next);
      }

      qsort(lines, stats->num_lines, sizeof(line_stats_t), sort_lines);

      for (line_cnt=0; line_cnt < stats->num_lines && 
       (min == -1 || lines[line_cnt].size > min); line_cnt++)
         fprintf(stderr, "%5ld allocations from line %4ld totalling %8ld "
          "bytes.\n", lines[line_cnt].number, lines[line_cnt].line, 
          lines[line_cnt].size);

      free(lines);
   }
}

long report_offenders(long min)
{
   track_t *temp;
   long key, pieces, bytes;
   iterator_t itr;

   str_table_t offenders;
   str_iterator_t str_itr;
   char *str_key;
   line_stats_t *line, **find;
   file_stats_t *stats, *next;
   file_stats_t *stat_array;

   if (table == NULL)
      return;

   bytes = pieces = 0;

   itr = create_iterator(table);
   offenders = create_str_table();

   while (next_item(itr, &key, (void **) &temp)) { 
      pieces++;
      bytes += temp->space;

      if ((stats = get_str_table_item(offenders, temp->file)) == NULL) {
         stats = (file_stats_t*)calloc(sizeof(file_stats_t), 1);
         stats->file = temp->file;
         add_str_table_item(offenders, temp->file, (void**)stats);
      }

      stats->size += temp->space;
      stats->number++;

      for (find=&stats->lines; *find != NULL && (*find)->line != temp->line; 
       find = &(*find)->next)
         ;

      if (*find == NULL) {
         *find = (line_stats_t*)calloc(sizeof(line_stats_t), 1);
         (*find)->line = temp->line;
         stats->num_lines++;
      }

      (*find)->size += temp->space;
      (*find)->number++;
   }

   destroy_iterator(itr);

   str_itr = create_str_table_iterator(offenders);

   next = stat_array = (file_stats_t*)malloc(sizeof(file_stats_t)*
    num_str_table_items(offenders));

   while (next_str_table_item(str_itr, &str_key, (void**)&stats)) {
      *next++ = *stats;
      free(stats);
   }
   
   destroy_str_table_iterator(str_itr);

   qsort(stat_array, num_str_table_items(offenders), sizeof(file_stats_t),
    sort_files);

   output_offenders(stat_array,  num_str_table_items(offenders), min);

   if (bytes != 0 || pieces != 0) {
      fprintf(stderr, "------------------------------------------------\n");
      fprintf(stderr, "%ld bytes unfreed space in %ld pieces.\n", 
       bytes, pieces);
      fprintf(stderr, "%ld malloc calls, %ld free calls.\n", malloc_count,
       free_count);
      fprintf(stderr, "------------------------------------------------\n");
   }
   
   free(stat_array);
   destroy_str_table(offenders);

   return bytes;
}

void *smartalloc(unsigned int bytes, char *file, int line, int fill)
{
   track_t *temp;
   register int i;
   char *data;

	/* debug */
	assert(bytes > 0);

   malloc_count++;
   
   if ((bytes + 2*BOUND_BYTES) < bytes) {
      fprintf(stderr, "Malloc failure of %ld bytes in file %s on line %d\n",
       bytes, file, line);
      assert(NO);
      exit(1);
   }

   if ((temp = (track_t *) malloc(sizeof(track_t))) == NULL) {
      fprintf(stderr, "Malloc failure of %ld bytes in file %s on line %d\n",
       bytes, file, line);
      assert(NO);
      exit(1);
   }
      
   if ((temp->data = data = malloc(bytes + 2*BOUND_BYTES)) == NULL) {
      fprintf(stderr, "Malloc failure of %ld bytes in file %s on line %d\n",
       bytes, file, line);
      assert(NO);
      exit(1);
   }

   data += BOUND_BYTES;
   
   for (i = 0; i < BOUND_BYTES; i++) {
      data[bytes + i] = BOUND;
      data[-1-i] = BOUND;
   }

   memset(data, fill, bytes);

   temp->space = bytes;
   temp->file = file;
   temp->line = line;

   if (table == NULL)
      table = create_table();

   if (add_item(table, ((long) data) >> 1, temp) != YES) {
      fprintf(stderr, 
       "Attempt to allocate something already allocated in %s at line %d!\n", 
        file, line);
      fprintf(stderr, "Internal memory heap has been corrupted.  Aborting.\n");
      assert(NO);
      exit(1);
   }
   
   return (void *)data;
}

void chk_tromp(char *file, int line)
{
   track_t *temp;
   register int i;
   long key, space;
   char *data;
   iterator_t itr;

   if (table == NULL)
      return;

   itr = create_iterator(table);

   while (next_item(itr, &key, (void **) &temp)) { 
      data = (char *) temp->data + BOUND_BYTES;
      space = temp->space;
      for (i = 0; i < BOUND_BYTES; i++)
         if (data[space + i] != BOUND || data[-1 - i] != BOUND) {
            fprintf(stderr, "Check_tromp failed from %s at line %d: \n", 
             file, line);
            fprintf(stderr, 
             "Memory at address 0x%p, allocated from file %s, line %d has been"
             " written past bounds.\n", data, temp->file, temp->line);
            break;
         }
   }

   destroy_iterator(itr);
}

void smartfree(void *data, char *file, int line)
{
   track_t *temp;
   void *address;
   register int i;

   free_count++;

   address = (char *) data - BOUND_BYTES;

   if (table == NULL)  {
      fprintf(stderr, "Attempt to free space when none has been malloced.\n"
       "Attempt made from file %s at line " "%d.\n", file, line);
      return;
   }

   if ((temp = get_item(table, ((long) data) >> 1)) == NULL) {
      fprintf(stderr, "Attempt to free non-malloced space at address 0x%p.\n"
       "Attempt made from file %s at line %d.\n", data, file, line);
      return;
   }

   for (i = 0; i < BOUND_BYTES; i++)
      if (((char *)data)[temp->space + i] != BOUND || 
       ((char *)data)[-1-i] != BOUND) {
         fprintf(stderr, 
          "Memory at address 0x%p, allocated from file %s, line %d has been"
          " written past bounds.\n", data, temp->file, temp->line);
         break;
      }

   memset(data, FREED, temp->space);

   destroy_item(table, ((long) data) >> 1);

   free(address);
   free(temp);
}

void *smartrealloc(void *old, unsigned int bytes, char *file, int line, 
 int fill)
{
   void *data;
   track_t *old_track;

   if ((old_track = get_item(table, ((long) old) >> 1)) == NULL) {
      fprintf(stderr, "Attempt to realloc something not allocated!\n");
      fprintf(stderr, "Realloc failure of %ld bytes in file %s at line %d.\n",
       bytes, file, line);
      assert(NO);
      exit(1);
   }

   data = smartalloc(bytes, file, line, fill);
   
   memcpy(data, old, bytes < old_track->space ? bytes : old_track->space);

   smartfree(old, file, line);

   return data;
}

char *smartdup(const char *oldstr, char *file, int line, int fill)
{
   void *data;
   unsigned int bytes;

   bytes = strlen((char *) oldstr) + 1;
   data = smartalloc(bytes, file, line, fill);

   memcpy(data, oldstr, bytes);

   return data;
}