/*  Author: Mark Moraes <moraes@csri.toronto.edu> */

/*LINTLIBRARY*/

#include "defs.h"
#include "globals.h"
#include "sptree.h"

RCSID("$Id: leak.c,v 1.1.1.1 1998/02/10 21:01:46 mea Exp $")

/* 
 *  These routines provide an interface for tracing memory leaks. The
 *  user can turn on leak tracing at any time by calling
 *  mal_leaktrace(1), after which every block allocated by
 *  _malloc()/_calloc()/_realloc()/_valloc()/_memalign() has a string
 *  (containing the filename and linenumber of the routine invoking it)
 *  stored in a database. When _free()/_cfree() is called on that block,
 *  the record is deleted from the database. The user can call
 *  mal_dumpleaktrace() to show the list of blocks allocated, and
 *  where they were allocated. The location of leaks can usually be
 *  detected from this.
 */
/*
 *  The tree implementation used to store the blocks is a splay-tree,
 *  using an implementation in C by Dave Brower (daveb@rtech.uucp),
 *  translated from Douglas Jones' original Pascal. However, any data
 *  structure that permits insert(), delete() and traverse()/apply() of
 *  key, value pairs should be suitable. Only this file needs to be
 *  changed.
 */
static SPTREE *sp = NULL;

/*
 *  num is a sequence number, incremented for ever block. min_num gets
 *  set to num after every dumpleaktrace - subsequent dumps do not print
 *  any blocks with sequence numbers less than min_num
 */
static unsigned long min_num = 0;
static unsigned long num = 0;

/*
 * These are used by mal_contents to count number of allocated blocks and the
 * number of bytes allocated.  Better way to do this is to walk the heap
 * rather than scan the splay tree.
 */
static unsigned long nmallocs;
static unsigned long nbytes;

static FILE *dumpfp = NULL;

/* 
 *  Turns recording of FILE and LINE number of each call to
 *  malloc/free/realloc/calloc/cfree/memalign/valloc on (if value != 0)
 *  or off, (if value == 0)
 */
void
mal_leaktrace(value)
int value;
{
	_malloc_leaktrace = (value != 0);
	if (sp == NULL)
		sp = __spinit();
}

/*
 *  The routine which actually does the printing. I know it is silly to
 *  print address in decimal, but sort doesn't read hex, so sorting the
 *  printed data by address is impossible otherwise. Urr. The format is
 *		FILE:LINE: sequence_number address_in_decimal (address_in_hex)
 */
void
__m_prnode(spblk)
SPBLK *spblk;
{
	if ((unsigned long) spblk->datb < min_num)
		return;
	(void) sprintf(_malloc_statsbuf, "%s%8lu %8lu(0x%08lx)\n",
		       (char *) spblk->data, (unsigned long) spblk->datb,
		       (unsigned long) spblk->key, (unsigned long) spblk->key);
	(void) fputs(_malloc_statsbuf, dumpfp);
}

/*
 *  Dumps all blocks which have been recorded.
 */
void
mal_dumpleaktrace(fp)
FILE *fp;
{
	dumpfp = fp;
	__spscan(__m_prnode, (SPBLK *) NULL, sp);
	(void) fflush(dumpfp);
	min_num = num;
}

/*
 *  Inserts a copy of a string keyed by the address addr into the tree
 *  that stores the leak trace information. The string is presumably of
 *  the form "file:linenumber:". It also stores a sequence number that
 *  gets incremented with each call to this routine.
 */
void
__m_install_record(addr, s)
univptr_t addr;
const char *s;
{
	num++;
	(void) __spadd(addr, strsave(s), (char *) num, sp);
}

/* Deletes the record keyed by addr if it exists */
void
__m_delete_record(addr)
univptr_t addr;
{
	SPBLK *result;

	if ((result = __splookup(addr, sp)) != NULL) {
		free(result->data);
		result->data = 0;
		__spdelete(result, sp);
	}
}

static void
__m_count(spblk)
SPBLK *spblk;
{
	Word *p;
	
	nmallocs++;
	p = (Word *) spblk->key;
	p -= HEADERWORDS;

	CHECKALLOCPTR(p, "when checking in __m_count");
	nbytes += SIZE(p) * sizeof(Word);
	return;
}

void
mal_contents(fp)
FILE *fp;
{
	nmallocs = 0;
	nbytes = 0;
	__spscan(__m_count, (SPBLK *) NULL, sp);
	(void) sprintf(_malloc_statsbuf,
		       "%% %lu bytes %lu mallocs %lu vm\n",
		       nbytes, nmallocs, (ulong) sbrk(0));
	(void) fputs(_malloc_statsbuf, fp);
	(void) fflush(fp);
}
