/*
 * $Id: malloc.c 13945 2007-06-24 00:42:32Z cbiere $
 *
 * Copyright (c) 2004, Raphael Manfredi
 *
 *----------------------------------------------------------------------
 * This file is part of gtk-gnutella.
 *
 *  gtk-gnutella 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.
 *
 *  gtk-gnutella 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 gtk-gnutella; if not, write to the Free Software
 *  Foundation, Inc.:
 *      59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *----------------------------------------------------------------------
 */

/**
 * @ingroup lib
 * @file
 *
 * Debugging malloc, to supplant dmalloc which is not satisfactory.
 *
 * @author Raphael Manfredi
 * @date 2004
 */

#include "common.h"		/* For RCSID */

#include "atoms.h"		/* For binary_hash() */
#include "misc.h"		/* For concat_strings() */
#include "tm.h"			/* For tm_time() */

#ifdef MALLOC_STATS
#ifndef TRACK_MALLOC
#define TRACK_MALLOC
#endif
#endif

/**
 * Routines in this file are defined either for TRACK_MALLOC or TRACK_ZALLOC
 */

#if defined(TRACK_MALLOC) || defined(TRACK_ZALLOC)
RCSID("$Id: malloc.c 13945 2007-06-24 00:42:32Z cbiere $")

/*
 * When MALLOC_FRAMES is supplied, we keep information about the allocation
 * stack frame and free stack frames.
 *
 * This turns on MALLOC_STATS automatically if not set.
 *
 * XXX need metaconfig checks for execinfo.h, backtrace() and
 * backtrace_symbols().  Also, when GNU ld is used, -rdynamic must be
 * added to the ld flags to get routine name information.
 */

#ifdef MALLOC_FRAMES
#include <execinfo.h>

#ifndef MALLOC_STATS
#define MALLOC_STATS
#endif

#define FRAME_DEPTH		8	/**< Size of allocation frame we keep around */

#endif /* MALLOC_FRAMES */
#endif /* TRACK_MALLOC || TRACK_ZALLOC */

#ifdef TRACK_MALLOC

#include "hashlist.h"
#include "misc.h"
#include "glib-missing.h"

#define MALLOC_SOURCE	/**< Avoid nasty remappings, but include signatures */
#include "override.h"

#if 0
#define TRANSPARENT		/**< To make sure our macros have no side effect */
#endif

static time_t init_time = 0;
static time_t reset_time = 0;

/**
 * Structure keeping track of allocated blocks.
 *
 * Each block is inserted into a hash table, the key being the block's
 * address and the value being a structure keeping track of the initial
 * allocation, and possibly of all the reallocations performed.
 */
struct block {
	gchar *file;
	gint line;
	size_t size;
	GSList *realloc;
};

static GHashTable *blocks = NULL;

static void free_record(gconstpointer o, gchar *file, gint line);

#ifdef MALLOC_FRAMES

/**
 * Structure keeping track of the allocation/free stack frames.
 *
 * Counts are signed because for realloc() frames, we count algebric
 * quantities (in case the blocks are shrunk).
 */
struct frame {
	void *stack[FRAME_DEPTH];	/**< PC of callers */
	gint len;					/**< Number of valid entries in stack */
	size_t count;				/**< Bytes allocated/freed since reset */
	size_t total_count;			/**< Grand total for this stack frame */
};

/**
 * Hashing routine for a "struct frame".
 */
static guint
frame_hash(gconstpointer key)
{
	const struct frame *f = key;

	return binary_hash(f->stack, f->len * sizeof(void *));
}

/**
 * Comparison of two "struct frame" structures.
 */
static gint
frame_eq(gconstpointer a, gconstpointer b)
{
	const struct frame *fa = a, *fb = b;

	return fa->len == fb->len &&
		0 == memcmp(fa->stack, fb->stack, fa->len * sizeof(void *));
}

#endif /* MALLOC_FRAMES */

/**
 * @struct stats
 *
 * When MALLOC_STATS is supplied, we keep information about the amount
 * of bytes allocated from a single point in the code, and the amount
 * of it that has been freed.
 *
 * When compiling with MALLOC_STATS, it's best to use REMAP_ZALLOC
 * as well since normally zalloc has its own block tracking features
 * that will not be accounted for in the malloc stats.
 */
#ifdef MALLOC_STATS

struct stats {
	gchar *file;				/**< Place where allocation took place */
	gint line;					/**< Line number */
	gint blocks;				/**< Live blocks since last "reset" */
	gint total_blocks;			/**< Total live blocks */
	size_t allocated;			/**< Total allocated since last "reset" */
	size_t freed;				/**< Total freed since last "reset" */
	size_t total_allocated;		/**< Total allocated overall */
	size_t total_freed;			/**< Total freed overall */
	size_t reallocated;			/**< Total reallocated since last "reset" */
	size_t total_reallocated;	/**< Total reallocated overall (algebric!) */
#ifdef MALLOC_FRAMES
	GHashTable *alloc_frames;	/**< The frames where allocation took place */
	GHashTable *free_frames;	/**< The frames where free took place */
	GHashTable *realloc_frames;	/**< The frames where realloc took place */
#endif /* MALLOC_FRAMES */
};

static GHashTable *stats = NULL; /**< maps stats(file, line) -> stats */

/**
 * Hashing routine for "struct stats".
 * Only the "file" and "line" fields are considered.
 */
static guint
stats_hash(gconstpointer key)
{
	const struct stats *s = key;

	return g_str_hash(s->file) ^ s->line;
}

/**
 * Comparison of two "struct stats" structures.
 * Only the "file" and "line" fields are considered.
 */
static gint
stats_eq(gconstpointer a, gconstpointer b)
{
	const struct stats *sa = a, *sb = b;

	return  sa->line == sb->line && 0 == strcmp(sa->file, sb->file);
}
#endif /* MALLOC_STATS */

/**
 * malloc_init
 *
 * Called at first allocation to initialize tracking structures.
 */
static void
malloc_init(void)
{
	blocks = g_hash_table_new(NULL, NULL);

#ifdef MALLOC_STATS
	stats = g_hash_table_new(stats_hash, stats_eq);
#endif

	init_time = reset_time = tm_time();
}

/**
 * malloc_log_block		-- hash table iterator callback
 *
 * Log used block, and record it among the `leaksort' set for future summary.
 */
static void
malloc_log_block(gpointer k, gpointer v, gpointer leaksort)
{
	struct block *b = v;

	g_warning("leaked block 0x%lx (%lu bytes) from \"%s:%d\"",
		(gulong) k, (gulong) b->size, b->file, b->line);

	leak_add(leaksort, b->size, b->file, b->line);

	if (b->realloc) {
		struct block *r = b->realloc->data;
		guint cnt = g_slist_length(b->realloc);

		g_warning("   (realloc'ed %u time%s, lastly from \"%s:%d\")",
			cnt, cnt == 1 ? "" : "s", r->file, r->line);
	}
}

/**
 * Dump all the blocks that are still used.
 */
void
malloc_close(void)
{
	gpointer leaksort;

	if (blocks == NULL)
		return;

#ifdef MALLOC_STATS
	g_warning("aggregated memory usage statistics:");
	alloc_dump(stderr, TRUE);
#endif

	leaksort = leak_init();

	g_hash_table_foreach(blocks, malloc_log_block, leaksort);

	leak_dump(leaksort);
	leak_close(leaksort);
}

/**
 * Record object `o' allocated at `file' and `line' of size `s'.
 * @return argument `o'.
 */
gpointer
malloc_record(gconstpointer o, size_t sz, gchar *file, gint line)
{
	struct block *b;
	struct block *ob;
#ifdef MALLOC_STATS
	struct stats *st;		/* Needed in case MALLOC_FRAMES is also set */
#endif

#ifdef TRANSPARENT
	return o;
#endif

	if (o == NULL)			/* In case it's called externally */
		return NULL;

	if (blocks == NULL)
		malloc_init();

	b = calloc(1, sizeof(*b));
	if (b == NULL)
		g_error("unable to allocate %u bytes", (unsigned) sizeof(*b));

	b->file = short_filename(file);
	b->line = line;
	b->size = sz;
	b->realloc = NULL;

	/**
	 * It can happen that we track the allocation of a block somewhere
	 * but the freeing happens somewhere we either we forgot to include
	 * "override.h", or happens in some library (e.g. in GTK+) where we
	 * can't record it.
	 *
	 * If we're "lucky" enough to see the address of such a block being
	 * reused again, then it has necessarily been freed, or malloc() would
	 * not reuse it again!  Fake a free from "FAKED:0".
	 */

	ob = g_hash_table_lookup(blocks, o);
	if (ob) {
		g_warning("(%s:%d) reusing block 0x%lx from %s:%d, missed its freeing",
			file, line, (gulong) o, ob->file, ob->line);
		free_record(o, "FAKED", 0);
	}

	gm_hash_table_insert_const(blocks, o, b);

#ifdef MALLOC_STATS
	{
		struct stats s;

		s.file = b->file;
		s.line = line;

		st = g_hash_table_lookup(stats, &s);

		if (st == NULL) {
			st = calloc(1, sizeof(*st));
			st->file = b->file;
			st->line = line;
			g_hash_table_insert(stats, st, st);
		}

		st->total_blocks++;
		st->blocks++;
		st->allocated += sz;
		st->total_allocated += sz;
	}
#endif /* MALLOC_STATS */
#ifdef MALLOC_FRAMES
	{
		struct frame f;
		struct frame *fr = NULL;

		f.len = backtrace(f.stack, G_N_ELEMENTS(f.stack));

		if (st->alloc_frames == NULL)
			st->alloc_frames = g_hash_table_new(frame_hash, frame_eq);
		else
			fr = g_hash_table_lookup(st->alloc_frames, &f);

		if (fr == NULL) {
			fr = calloc(1, sizeof(*fr));
			memcpy(fr->stack, f.stack, f.len * sizeof(void *));
			fr->len = f.len;
			g_hash_table_insert(st->alloc_frames, fr, fr);
		}

		fr->count += sz;
		fr->total_count += sz;
	}
#endif /* MALLOC_FRAMES */

	return deconstify_gpointer(o);
}

/**
 * Allocate `s' bytes.
 */
gpointer
malloc_track(size_t size, gchar *file, gint line)
{
	gpointer o;

	o = malloc(size);
	if (o == NULL)
		g_error("unable to allocate %lu bytes", (gulong) size);

	return malloc_record(o, size, file, line);
}

/**
 * Allocate `s' bytes, zero the allocated zone.
 */
gpointer
malloc0_track(size_t size, gchar *file, gint line)
{
	gpointer o;

	o = malloc_track(size, file, line);
	memset(o, 0, size);

	return o;
}

/**
 * Record freeing of allocated block.
 */
static void
free_record(gconstpointer o, gchar *file, gint line)
{
	struct block *b;
	gpointer k;
	gpointer v;
	GSList *l;
#ifdef MALLOC_STATS
	struct stats *st;		/* Needed in case MALLOC_FRAMES is also set */
#endif

	if (NULL == o)
		return;

	if (blocks == NULL || !(g_hash_table_lookup_extended(blocks, o, &k, &v))) {
		g_warning("(%s:%d) attempt to free block at 0x%lx twice?",
			file, line, (gulong) o);
		return;
	}

	b = v;
	g_assert(o == k);

#ifdef MALLOC_STATS
	{
		struct stats s;

		s.file = b->file;
		s.line = b->line;

		st = g_hash_table_lookup(stats, &s);

		if (st == NULL)
			g_warning("(%s:%d) no allocation record of block 0x%lx from %s:%d?",
				file, line, (gulong) o, b->file, b->line);
		else {
			/* Count present block size, after possible realloc() */
			st->freed += b->size;
			st->total_freed += b->size;
			if (st->total_blocks > 0)
				st->total_blocks--;
			else
				g_warning("(%s:%d) live # of blocks was zero at free time?",
					file, line);

			/* We could free blocks allocated before "reset", don't warn */
			if (st->blocks > 0)
				st->blocks--;
		}
	}
#endif /* MALLOC_STATS */
#ifdef MALLOC_FRAMES
	if (st != NULL) {
		struct frame f;
		struct frame *fr = NULL;

		f.len = backtrace(f.stack, G_N_ELEMENTS(f.stack));

		if (st->free_frames == NULL)
			st->free_frames = g_hash_table_new(frame_hash, frame_eq);
		else
			fr = g_hash_table_lookup(st->free_frames, &f);

		if (fr == NULL) {
			fr = calloc(1, sizeof(*fr));
			memcpy(fr->stack, f.stack, f.len * sizeof(void *));
			fr->len = f.len;
			g_hash_table_insert(st->free_frames, fr, fr);
		}

		fr->count += b->size;			/* Counts actual size, not original */
		fr->total_count += b->size;
	}
#endif /* MALLOC_FRAMES */

	g_hash_table_remove(blocks, o);

	for (l = b->realloc; l; l = g_slist_next(l)) {
		struct block *r = l->data;
		g_assert(r->realloc == NULL);
		free(r);
	}
	g_slist_free(b->realloc);

	free(b);
}

/**
 * Free allocated block.
 */
void
free_track(gpointer o, gchar *file, gint line)
{
#ifndef TRANSPARENT
	free_record(o, file, line);
#endif
	free(o);
}

/**
 * Free NULL-terminated vector of strings, and the vector.
 */
void
strfreev_track(gchar **v, gchar *file, gint line)
{
	gchar *x;
	gchar **iv = v;

	while ((x = *iv++))
		free_track(x, file, line);

	free_track(v, file, line);
}

/**
 * Update data structures to record that block `o' was re-alloced into
 * a block of `s' bytes at `n'.
 */
static gpointer
realloc_record(gpointer o, gpointer n, size_t size, gchar *file, gint line)
{
	struct block *b;
	struct block *r;
#ifdef MALLOC_STATS
	struct stats *st;		/* Needed in case MALLOC_FRAMES is also set */
#endif

	g_assert(n);

	if (blocks == NULL || !(b = g_hash_table_lookup(blocks, o))) {
		g_warning("(%s:%d) attempt to realloc freed block at 0x%lx?",
			file, line, (gulong) o);
		return malloc_record(n, size, file, line);
	}

	r = calloc(sizeof(*r), 1);
	if (r == NULL)
		g_error("unable to allocate %u bytes", (unsigned) sizeof(*r));

	r->file = short_filename(file);
	r->line = line;
	r->size = b->size;			/* Previous size before realloc */
	r->realloc = NULL;

	b->realloc = g_slist_prepend(b->realloc, r);	/* Last realloc at head */
	b->size = size;

	if (n != o) {
		g_hash_table_remove(blocks, o);
		g_hash_table_insert(blocks, n, b);
	}

#ifdef MALLOC_STATS
	{
		struct stats s;

		s.file = b->file;
		s.line = b->line;

		st = g_hash_table_lookup(stats, &s);

		if (st == NULL)
			g_warning("(%s:%d) no allocation record of block 0x%lx from %s:%d?",
				file, line, (gulong) o, b->file, b->line);
		else {
			/* We store variations in size, as algebric quantities */
			st->reallocated += b->size - r->size;
			st->total_reallocated += b->size - r->size;
		}
	}
#endif /* MALLOC_STATS */
#ifdef MALLOC_FRAMES
	if (st != NULL) {
		struct frame f;
		struct frame *fr = NULL;

		f.len = backtrace(f.stack, G_N_ELEMENTS(f.stack));

		if (st->realloc_frames == NULL)
			st->realloc_frames = g_hash_table_new(frame_hash, frame_eq);
		else
			fr = g_hash_table_lookup(st->realloc_frames, &f);

		if (fr == NULL) {
			fr = calloc(1, sizeof(*fr));
			memcpy(fr->stack, f.stack, f.len * sizeof(void *));
			fr->len = f.len;
			g_hash_table_insert(st->realloc_frames, fr, fr);
		}

		fr->count += b->size - r->size;
		fr->total_count += b->size - r->size;
	}
#endif /* MALLOC_FRAMES */

	return n;
}

/**
 * Realloc object `o' to `size' bytes.
 */
gpointer
realloc_track(gpointer o, size_t size, gchar *file, gint line)
{
	if (o == NULL)
		return malloc_track(size, file, line);

#ifdef TRANSPARENT
	return realloc(o, size);
#endif

	if (0 == size) {
		free_track(o, file, line);
		return NULL;
	} else {
		gpointer n;

		n = realloc(o, size);
		if (n == NULL)
			g_error("cannot realloc block into a %lu-byte one", (gulong) size);

		return realloc_record(o, n, size, file, line);
	}
}

/**
 * Duplicate buffer `p' of length `size'.
 */
gpointer
memdup_track(gconstpointer p, size_t size, gchar *file, gint line)
{
	gpointer o;

	if (p == NULL)
		return NULL;

	o = malloc_track(size, file, line);
	memcpy(o, p, size);

	return o;
}

/**
 * Duplicate string `s'.
 */
gchar *
strdup_track(const gchar *s, gchar *file, gint line)
{
	gpointer o;
	size_t len;

	if (s == NULL)
		return NULL;

	len = strlen(s);
	o = malloc_track(len + 1, file, line);
	memcpy(o, s, len + 1);		/* Also copy trailing NUL */

	return o;
}

/**
 * Duplicate string `s', on at most `n' chars.
 */
gchar *
strndup_track(const gchar *s, size_t n, gchar *file, gint line)
{
	gpointer o;
	gchar *q;

	if (s == NULL)
		return NULL;

	o = malloc_track(n + 1, file, line);
	q = o;
	while (n-- > 0 && '\0' != (*q = *s++)) {
		q++;
	}
	*q = '\0';

	return o;
}

/**
 * Join items in `vec' with `s' in-between.
 */
gchar *
strjoinv_track(const gchar *s, gchar **vec, gchar *file, gint line)
{
	gchar *o;

	o = g_strjoinv(s, vec);

	return malloc_record(o, strlen(o) + 1, file, line);
}

/**
 * The internal implementation of a vectorized g_strconcat().
 */
static gchar *
m_strconcatv(const gchar *s, va_list args)
{
	gchar *res;
	gchar *add;
	size_t size;

	size = strlen(s) + 1;
	res = g_malloc(size);
	memcpy(res, s, size);

	while ((add = va_arg(args, gchar *))) {
		size_t len = strlen(add);
		res = g_realloc(res, size + len);
		memcpy(res + size - 1, add, len + 1);	/* Includes trailing NULL */
		size += len;
	}

	return res;
}

/**
 * Perform string concatenation, returning newly allocated string.
 */
gchar *
strconcat_track(gchar *file, gint line, const gchar *s, ...)
{
	va_list args;
	gchar *o;

	va_start(args, s);
	o = m_strconcatv(s, args);
	va_end(args);

	return malloc_record(o, strlen(o) + 1, file, line);
}

/**
 * Perform printf into newly allocated string.
 */
gchar *
strdup_printf_track(gchar *file, gint line, const gchar *fmt, ...)
{
	va_list args;
	gchar *o;

	va_start(args, fmt);
	o = g_strdup_vprintf(fmt, args);
	va_end(args);

	return malloc_record(o, strlen(o) + 1, file, line);
}

/**
 * Perform a g_strplit() operation, tracking all returned strings.
 */
gchar **
strsplit_track(const gchar *s, const gchar *d, size_t m, gchar *file, gint line)
{
	gchar **v;
	gchar **iv;
	gchar *x;

	v = g_strsplit(s, d, m);
	malloc_record(v, (m + 1) * sizeof(gchar *), file, line);

	iv = v;
	while ((x = *iv++))
		malloc_record(x, strlen(x) + 1, file, line);

	return v;
}

/**
 * Record string `s' allocated at `file' and `line'.
 * @return argument `s'.
 */
gchar *
string_record(const gchar *s, gchar *file, gint line)
{
	if (s == NULL)
		return NULL;

	return malloc_record(s, strlen(s) + 1, file, line);
}

/**
 * Wrapper over g_hash_table_new() to track allocation of hash tables.
 */
GHashTable *
hashtable_new_track(GHashFunc h, GCompareFunc y, gchar *file, gint line)
{
	const size_t size = 7 * sizeof(void *);	/* Estimated size */
	GHashTable *o;

	o = g_hash_table_new(h, y);
	return malloc_record(o, size, file, line);
}

/**
 * Wrapper over g_hash_Table_destroy() to track destruction of hash tables.
 */
void
hashtable_destroy_track(GHashTable *h, gchar *file, gint line)
{
	free_record(h, file, line);
	g_hash_table_destroy(h);
}

/**
 * Wrapper over hash_list_new().
 */
hash_list_t *
hash_list_new_track(
	GHashFunc hash_func, GEqualFunc eq_func, gchar *file, gint line)
{
	return malloc_record(
		hash_list_new(hash_func, eq_func),
		28,				/* Approx. size */
		file, line);
}

/**
 * Wrapper over hash_list_free().
 */
void
hash_list_free_track(hash_list_t **hl_ptr, gchar *file, gint line)
{
	if (*hl_ptr) {
		free_record(*hl_ptr, file, line);
		hash_list_free(hl_ptr);
	}
}

/***
 *** List trackers, to unveil hidden linkable allocation.
 ***/

/**
 * Record GSList `list' allocated at `file' and `line'.
 * @return argument `list'.
 */
GSList *
gslist_record(const GSList * const list, gchar *file, gint line)
{
	const GSList *iter;

	for (iter = list; NULL != iter; iter = g_slist_next(iter)) {
		malloc_record(iter, sizeof *iter, file, line);
	}
	return deconstify_gpointer(list);
}

GSList *
track_slist_alloc(gchar *file, gint line)
{
	return malloc_record(g_slist_alloc(), sizeof(GSList), file, line);
}

GSList *
track_slist_append(GSList *l, gpointer data, gchar *file, gint line)
{
	GSList *new;

	new = track_slist_alloc(file, line);
	new->data = data;

	if (l) {
		GSList *last = g_slist_last(l);
		last->next = new;
		return l;
	} else
		return new;
}

GSList *
track_slist_prepend(GSList *l, gpointer data, gchar *file, gint line)
{
	GSList *new;

	new = track_slist_alloc(file, line);
	new->data = data;
	new->next = l;

	return new;
}

GSList *
track_slist_copy(GSList *list, gchar *file, gint line)
{
	return gslist_record(g_slist_copy(list), file, line);
}

void
track_slist_free(GSList *l, gchar *file, gint line)
{
	GSList *lk;

	for (lk = l; lk; lk = g_slist_next(lk))
		free_record(lk, file, line);

	g_slist_free(l);
}

void
track_slist_free1(GSList *l, gchar *file, gint line)
{
	if (l == NULL)
		return;

	free_record(l, file, line);
	g_slist_free_1(l);
}

GSList *
track_slist_remove(GSList *l, gpointer data, gchar *file, gint line)
{
	GSList *lk;

	lk = g_slist_find(l, data);
	if (lk == NULL)
		return l;

	return track_slist_delete_link(l, lk, file, line);
}

GSList *
track_slist_delete_link(GSList *l, GSList *lk, gchar *file, gint line)
{
	GSList *new;

	new = g_slist_remove_link(l, lk);
	track_slist_free1(lk, file, line);

	return new;
}

GSList *
track_slist_insert(GSList *l, gpointer data, gint pos, gchar *file, gint line)
{
	GSList *lk;

	if (pos < 0)
		return track_slist_append(l, data, file, line);
	else if (pos == 0)
		return track_slist_prepend(l, data, file, line);

	lk = g_slist_nth(l, pos - 1);
	if (lk == NULL)
		return track_slist_append(l, data, file, line);
	else
		return track_slist_insert_after(l, lk, data, file, line);
}

GSList *
track_slist_insert_sorted(GSList *l, gpointer d, GCompareFunc c,
	gchar *file, gint line)
{
	gint cmp;
	GSList *tmp = l;
	GSList *prev = NULL;
	GSList *new;

	if (l == NULL)
		return track_slist_prepend(l, d, file, line);

	cmp = (*c)(d, tmp->data);
	while (tmp->next != NULL && cmp > 0) {
		prev = tmp;
		tmp = tmp->next;
		cmp = (*c)(d, tmp->data);
	}

	new = track_slist_alloc(file, line);
	new->data = d;

	if (tmp->next == NULL && cmp > 0) {
		tmp->next = new;
		return l;
	}

	if (prev != NULL) {
		prev->next = new;
		new->next = tmp;
		return l;
	}

	new->next = l;
	return new;
}

GSList *
track_slist_insert_after(GSList *l, GSList *lk, gpointer data,
	gchar *file, gint line)
{
	GSList *new;

	if (lk == NULL)
		return track_slist_prepend(l, data, file, line);

	new = track_slist_alloc(file, line);
	new->data = data;

	new->next = lk->next;
	lk->next = new;

	return l;
}

GList *
track_list_alloc(gchar *file, gint line)
{
	return malloc_record(g_list_alloc(), sizeof(GList), file, line);
}

GList *
track_list_append(GList *l, gpointer data, gchar *file, gint line)
{
	GList *new;

	new = track_list_alloc(file, line);
	new->data = data;

	if (l) {
		GList *last = g_list_last(l);
		last->next = new;
		new->prev = last;
		return l;
	} else
		return new;
}

GList *
track_list_prepend(GList *l, gpointer data, gchar *file, gint line)
{
	GList *new;

	new = track_list_alloc(file, line);
	new->data = data;

	if (l) {
		if (l->prev) {
			l->prev->next = new;
			new->prev = l->prev;
		}
		l->prev = new;
		new->next = l;
	}

	return new;
}

/**
 * Record GList `list' allocated at `file' and `line'.
 * @return argument `list'.
 */
GList *
glist_record(const GList * const list, gchar *file, gint line)
{
	const GList *iter;

	for (iter = list; NULL != iter; iter = g_list_next(iter)) {
		malloc_record(iter, sizeof *iter, file, line);
	}
	return deconstify_gpointer(list);
}


GList *
track_list_copy(GList *list, gchar *file, gint line)
{
	return glist_record(g_list_copy(list), file, line);
}

void
track_list_free(GList *l, gchar *file, gint line)
{
	GList *lk;

	for (lk = l; lk; lk = g_list_next(lk))
		free_record(lk, file, line);

	g_list_free(l);
}

void
track_list_free1(GList *l, gchar *file, gint line)
{
	if (l == NULL)
		return;

	free_record(l, file, line);
	g_list_free_1(l);
}

GList *
track_list_remove(GList *l, gpointer data, gchar *file, gint line)
{
	GList *lk;

	lk = g_list_find(l, data);
	if (lk == NULL)
		return l;

	return track_list_delete_link(l, lk, file, line);
}

GList *
track_list_insert(GList *l, gpointer data, gint pos, gchar *file, gint line)
{
	GList *lk;

	if (pos < 0)
		return track_list_append(l, data, file, line);
	else if (pos == 0)
		return track_list_prepend(l, data, file, line);

	lk = g_list_nth(l, pos - 1);
	if (lk == NULL)
		return track_list_append(l, data, file, line);
	else
		return track_list_insert_after(l, lk, data, file, line);
}

GList *
track_list_insert_sorted(GList *l, gpointer d, GCompareFunc c,
	gchar *file, gint line)
{
	gint cmp;
	GList *tmp = l;
	GList *new;

	if (l == NULL)
		return track_list_prepend(l, d, file, line);

	cmp = (*c)(d, tmp->data);
	while (tmp->next != NULL && cmp > 0) {
		tmp = tmp->next;
		cmp = (*c)(d, tmp->data);
	}

	new = track_list_alloc(file, line);
	new->data = d;

	if (tmp->next == NULL && cmp > 0) {
		tmp->next = new;
		new->prev = tmp;
		return l;
	}

	/* Insert `new' before `tmp' */

	if (tmp->prev != NULL) {
		tmp->prev->next = new;
		new->prev = tmp->prev;
	}

	new->next = tmp;
	tmp->prev = new;

	return (tmp == l) ? new : l;
}

GList *
track_list_insert_after(GList *l, GList *lk, gpointer data,
	gchar *file, gint line)
{
	GList *new;

	if (lk == NULL)
		return track_list_prepend(l, data, file, line);

	new = track_list_alloc(file, line);
	new->data = data;

	new->prev = lk;
	new->next = lk->next;

	if (lk->next)
		lk->next->prev = new;

	lk->next = new;

	return l;
}

GList *
track_list_delete_link(GList *l, GList *lk, gchar *file, gint line)
{
	GList *new;

	new = g_list_remove_link(l, lk);
	track_list_free1(lk, file, line);

	return new;
}

/***
 *** String trackers, to unveil hidden string buffer allocation.
 ***/

#define GSTRING_OBJ_SIZE	(3 * sizeof(void *))		/* Estimated size */

/**
 * string_str_track
 *
 * Track changes to the internal string object.
 * @return GString object.
 */
static GString *
string_str_track(GString *s, gchar *old, gchar *file, gint line)
{
	size_t size;
#if GLIB_CHECK_VERSION(2,0,0)
	size = s->allocated_len;
#else
	size = s->len + 1;
#endif 

	if (s->str != old) {
		free_record(old, file, line);
		malloc_record(s->str, size, file, line);
	} else {
		realloc_record(s->str, s->str, size, file, line);
	}
	return s;
}

GString *
string_new_track(const gchar *p, gchar *file, gint line)
{
	GString *result = g_string_new(p);

	malloc_record(result, GSTRING_OBJ_SIZE, file, line);
	return string_str_track(result, NULL, file, line);
}

GString *
string_sized_new_track(size_t size, gchar *file, gint line)
{
	GString *result = g_string_sized_new(size);

	malloc_record(result, GSTRING_OBJ_SIZE, file, line);
	return string_str_track(result, NULL, file, line);
}

GString *
string_append_track(GString *s, const gchar *p, gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_append(s, p);
	return string_str_track(s, old, file, line);
}

GString *
string_append_c_track(GString *s, gchar c, gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_append_c(s, c);
	return string_str_track(s, old, file, line);
}

GString *
string_append_len_track(GString *s, const gchar *val, gssize len,
	gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_append_len(s, val, len);
	return string_str_track(s, old, file, line);
}

GString *
string_assign_track(GString *s, const gchar *p, gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_assign(s, p);
	return string_str_track(s, old, file, line);
}

void
string_free_track(GString *s, gint freestr, gchar *file, gint line)
{
	free_record(s, file, line);
	if (freestr)
		free_record(s->str, file, line);

	g_string_free(s, freestr);
}

GString *
string_prepend_track(GString *s, const gchar *p, gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_prepend(s, p);
	return string_str_track(s, old, file, line);
}

GString *
string_prepend_c_track(GString *s, gchar c, gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_prepend_c(s, c);
	return string_str_track(s, old, file, line);
}

GString *
string_insert_track(GString *s, gint pos, const gchar *p,
	gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_insert(s, pos, p);
	return string_str_track(s, old, file, line);
}

GString *
string_insert_c_track(GString *s, gint pos, gchar c, gchar *file, gint line)
{
	gchar *old = s->str;

	s = g_string_insert_c(s, pos, c);
	return string_str_track(s, old, file, line);
}

GString *
string_sprintf_track(GString *s, gchar *file, gint line, const gchar *fmt, ...)
{
	va_list args;
	gchar *o;
	gchar *old = s->str;

	va_start(args, fmt);
	o = g_strdup_vprintf(fmt, args);
	va_end(args);

	g_string_assign(s, o);
	g_free(o);
	return string_str_track(s, old, file, line);
}

GString *
string_sprintfa_track(GString *s, gchar *file, gint line, const gchar *fmt, ...)
{
	va_list args;
	gchar *o;
	gchar *old = s->str;

	va_start(args, fmt);
	o = g_strdup_vprintf(fmt, args);
	va_end(args);

	g_string_append(s, o);
	g_free(o);
	return string_str_track(s, old, file, line);
}

#endif /* TRACK_MALLOC */

/***
 *** This section contains general-purpose leak summarizing routines that
 *** can be used by both malloc() and zalloc().
 ***/

#if defined(TRACK_MALLOC) || defined(TRACK_ZALLOC)

struct leak_record {		/* Informations about leak at some place */
	size_t size;			/* Total size allocated there */
	size_t count;			/* Amount of allocations */
};

struct leak_set {
	GHashTable *places;		/* Maps "file:4" -> leak_record */
};

/**
 * Initialize the leak accumulator by "file:line"
 */
gpointer leak_init(void)
{
	struct leak_set *ls;

	ls = malloc(sizeof *ls);
	ls->places = g_hash_table_new(g_str_hash, g_str_equal);

	return ls;
}

/**
 * Get rid of the key/value tupple in the leak table.
 */
static gboolean
leak_free_kv(gpointer key, gpointer value, gpointer unused_user)
{
	(void) unused_user;
	free(key);
	free(value);
	return TRUE;
}

/**
 * Dispose of the leaks accumulated.
 */
void
leak_close(gpointer o)
{
	struct leak_set *ls = o;

	g_hash_table_foreach_remove(ls->places, leak_free_kv, NULL);
	g_hash_table_destroy(ls->places);

	free(ls);
}

/**
 * Record a new leak of `size' bytes allocated at `file', line `line'.
 */
void
leak_add(gpointer o, size_t size, gchar *file, gint line)
{
	struct leak_set *ls = o;
	gchar key[1024];
	struct leak_record *lr;
	gboolean found;
	gpointer k;
	gpointer v;

	g_assert(file);
	g_assert(line >= 0);

	concat_strings(key, sizeof key,
		file, ":", uint64_to_string(line), (void *) 0);
	found = g_hash_table_lookup_extended(ls->places, key, &k, &v);

	if (found) {
		lr = v;
		lr->size += size;
		lr->count++;
	} else {
		lr = malloc(sizeof(*lr));
		lr->size = size;
		lr->count = 1;
		g_hash_table_insert(ls->places, g_strdup(key), lr);
	}
}

struct leak {			/* A memory leak, for sorting purposes */
	gchar *place;
	struct leak_record *lr;
};

/**
 * leak_size_cmp		-- qsort() callback
 *
 * Compare two pointers to "struct leak" based on their size value,
 * in reverse order.
 */
static gint
leak_size_cmp(const void *p1, const void *p2)
{
	const struct leak *leak1 = p1, *leak2 = p2;

	/* Reverse order: largest first */
	return CMP(leak2->lr->size, leak1->lr->size);
}

struct filler {			/* Used by hash table iterator to fill leak array */
	struct leak *leaks;
	gint count;			/* Size of `leaks' array */
	gint idx;			/* Next index to be filled */
};

/**
 * fill_array			-- hash table iterator
 *
 * Append current hash table entry at the end of the "leaks" array.
 */
static void
fill_array(gpointer key, gpointer value, gpointer user)
{
	struct filler *filler = user;
	struct leak *l;
	struct leak_record *lr = value;

	g_assert(filler->idx < filler->count);

	l = &filler->leaks[filler->idx++];
	l->place = (gchar *) key;
	l->lr = lr;
}

/**
 * Dump the links sorted by decreasing leak size.
 */
void
leak_dump(gpointer o)
{
	struct leak_set *ls =  o;
	gint count;
	struct filler filler;
	gint i;

	count = g_hash_table_size(ls->places);

	if (count == 0)
		return;

	filler.leaks = malloc(sizeof(struct leak) * count);
	filler.count = count;
	filler.idx = 0;

	/*
	 * Linearize hash table into an array before sorting it by
	 * decreasing leak size.
	 */

	g_hash_table_foreach(ls->places, fill_array, &filler);
	qsort(filler.leaks, count, sizeof(struct leak), leak_size_cmp);

	/*
	 * Dump the leaks.
	 */

	g_warning("leak summary by total decreasing size:");
	g_warning("leaks found: %d", count);

	for (i = 0; i < count; i++) {
		struct leak *l = &filler.leaks[i];
		g_warning("%lu bytes (%lu block%s) from \"%s\"",
			(gulong) l->lr->size, (gulong) l->lr->count,
			l->lr->count == 1 ? "" : "s", l->place);
	}

	free(filler.leaks);
}

#endif /* TRACK_MALLOC || TRACK_ZALLOC */

/***
 *** This section contains general-purpose allocation summarizing routines that
 *** are used when MALLOC_STATS is on.
 ***
 *** This is used to spot the places where allocation takes place, sorted
 *** by decreasing allocation size.
 ***/

#ifdef MALLOC_STATS

struct afiller {		/* Used by hash table iterator to fill alloc array */
	struct stats **stats;
	gint count;			/* Size of `stats' array */
	gint idx;			/* Next index to be filled */
};

/**
 * Compare two pointers to "struct stat" based on their allocation value,
 * in reverse order. -- qsort() callback
 */
static gint
stats_allocated_cmp(const void *p1, const void *p2)
{
	const struct stats * const *s1 = p1, * const *s2 = p2;

	/* Reverse order: largest first */
	return CMP((*s2)->allocated, (*s1)->allocated);
}

/**
 * Compare two pointers to "struct stat" based on their total allocation value,
 * in reverse order. -- qsort() callback
 */
static gint
stats_total_allocated_cmp(const void *p1, const void *p2)
{
	const struct stats * const *s1 = p1, * const *s2 = p2;

	/* Reverse order: largest first */
	return CMP((*s2)->total_allocated, (*s1)->total_allocated);
}

/**
 * Compare two pointers to "struct stat" based on their residual value,
 * in reverse order. -- qsort() callback
 */
static gint
stats_residual_cmp(const void *p1, const void *p2)
{
	const struct stats * const *s1_ptr = p1, * const *s2_ptr = p2;
	const struct stats *s1 = *s1_ptr, *s2 = *s2_ptr;
	size_t i1 = s1->allocated + s1->reallocated - s1->freed;
	size_t i2 = s2->allocated + s2->reallocated - s2->freed;
	gint ret;

	/* Reverse order: largest first */
	ret = CMP(i2, i1);
	return ret ? ret : stats_allocated_cmp(p1, p2);
}

/**
 * Compare two pointers to "struct stat" based on their total residual value,
 * in reverse order. -- qsort() callback
 */
static gint
stats_total_residual_cmp(const void *p1, const void *p2)
{
	const struct stats * const *s1_ptr = p1, * const *s2_ptr = p2;
	const struct stats *s1 = *s1_ptr, *s2 = *s2_ptr;
	size_t i1 = s1->total_allocated + s1->total_reallocated - s1->total_freed;
	size_t i2 = s2->total_allocated + s2->total_reallocated - s2->total_freed;
	gint ret;

	/* Reverse order: largest first */
	ret = CMP(i2, i1);
	return ret ? ret : stats_allocated_cmp(p1, p2);
}

/**
 * Append current hash table entry at the end of the "stats" array
 * in the supplied filler structure.  -- hash table iterator
 */
static void
stats_fill_array(gpointer unused_key, gpointer value, gpointer user)
{
	struct afiller *filler = user;
	struct stats *st = value;
	struct stats **e;

	(void) unused_key;

	g_assert(filler->idx < filler->count);

	e = &filler->stats[filler->idx++];
	*e = st;
}

/**
 * Dump the stats held in the specified array.
 */
static void
stats_array_dump(FILE *f, struct afiller *filler)
{
	gint i;

	fprintf(f, "%7s %7s %8s %8s %4s [%7s %7s %8s %8s %6s] #a #f #r %s:\n",
		"alloc", "freed", "realloc", "remains", "live",
		"alloc", "freed", "realloc", "remains", "live", "from");

	for (i = 0; i < filler->count; i++) {
		struct stats *st = filler->stats[i];
		gint alloc_stacks;
		gint free_stacks;
		gint realloc_stacks;
		gint remains = st->allocated + st->reallocated - st->freed;
		gint total_remains =
			st->total_allocated + st->total_reallocated - st->total_freed;
		gchar *c_allocated = strdup(compact_size(st->allocated, TRUE));
		gchar *c_freed = strdup(compact_size(st->freed, TRUE));
		gchar *c_reallocated = strdup(compact_size(ABS(st->reallocated), TRUE));
		gchar *c_remains = strdup(compact_size(ABS(remains), TRUE));
		gchar *c_tallocated = strdup(compact_size(st->total_allocated, TRUE));
		gchar *c_tfreed = strdup(compact_size(st->total_freed, TRUE));
		gchar *c_treallocated =
			strdup(compact_size(ABS(st->total_reallocated), TRUE));
		gchar *c_tremains = strdup(compact_size(ABS(total_remains), TRUE));

#ifdef MALLOC_FRAMES
		alloc_stacks = st->alloc_frames == NULL ?
			0 : g_hash_table_size(st->alloc_frames);
		free_stacks = st->free_frames == NULL ?
			0 : g_hash_table_size(st->free_frames);
		realloc_stacks = st->realloc_frames == NULL ?
			0 : g_hash_table_size(st->realloc_frames);
#else
		alloc_stacks = free_stacks = realloc_stacks = 0;
#endif

		fprintf(f, "%7s %7s %c%7s %c%7s %4d [%7s %7s %c%7s %c%7s %6d] "
			"%2d %2d %2d \"%s:%d\"\n",
			c_allocated, c_freed,
			st->reallocated < 0 ? '-' : ' ', c_reallocated,
			remains < 0 ? '-' : ' ', c_remains,
			MIN(st->blocks, 9999),
			c_tallocated, c_tfreed,
			st->total_reallocated < 0 ? '-' : ' ', c_treallocated,
			total_remains < 0 ? '-' : ' ', c_tremains,
			MIN(st->total_blocks, 999999),
			MIN(alloc_stacks, 99),
			MIN(free_stacks, 99),
			MIN(realloc_stacks, 99),
			st->file, st->line);

		free(c_allocated);
		free(c_freed);
		free(c_reallocated);
		free(c_remains);
		free(c_tallocated);
		free(c_tfreed);
		free(c_treallocated);
		free(c_tremains);
	}
}

/**
 * Dump the allocation sorted by decreasing amount size on specified file.
 * When `total' is TRUE, sorting is made on the total stats instead of
 * the incremental ones.
 */
void
alloc_dump(FILE *f, gboolean total)
{
	gint count;
	struct afiller filler;
	time_t now;

	count = g_hash_table_size(stats);

	if (count == 0)
		return;

	now = tm_time();
	fprintf(f, "--- distinct allocation spots found: %d at %s\n",
		count, short_time(now - init_time));

	filler.stats = malloc(sizeof(struct stats *) * count);
	filler.count = count;
	filler.idx = 0;

	/*
	 * Linearize hash table into an array before sorting it by
	 * decreasing allocation size.
	 */

	g_hash_table_foreach(stats, stats_fill_array, &filler);
	qsort(filler.stats, count, sizeof(struct stats *),
		total ? stats_total_allocated_cmp : stats_allocated_cmp);

	/*
	 * Dump the allocation based on allocation sizes.
	 */

	fprintf(f, "--- summary by decreasing %s allocation size %s %s:\n",
		total ? "total" : "incremental", total ? "at" : "after",
		short_time(now - (total ? init_time : reset_time)));
	stats_array_dump(f, &filler);

	/*
	 * Now linearize hash table by decreasing residual allocation size.
	 */

	filler.idx = 0;

	g_hash_table_foreach(stats, stats_fill_array, &filler);
	qsort(filler.stats, count, sizeof(struct stats *),
		total ? stats_total_residual_cmp : stats_residual_cmp);

	fprintf(f, "--- summary by decreasing %s residual memory size %s %s:\n",
		total ? "total" : "incremental", total ? "at" : "after",
		short_time(now - (total ? init_time : reset_time)));
	stats_array_dump(f, &filler);

	fprintf(f, "--- end summary at %s\n", short_time(now - init_time));

	free(filler.stats);
}

/**
 * Reset incremental allocation and free counters. -- hash table iterator
 */
static void
stats_reset(gpointer uu_key, gpointer value, gpointer uu_user)
{
	struct stats *st = value;

	(void) uu_key;
	(void) uu_user;

	st->blocks = st->allocated = st->freed = st->reallocated = 0;
}

/**
 * Atomically dump the allocation stats and reset the incremental allocation
 * statistics.
 */
void
alloc_reset(FILE *f, gboolean total)
{
	time_t now = tm_time();

	alloc_dump(f, total);
	g_hash_table_foreach(stats, stats_reset, NULL);

	fprintf(f, "--- incremental allocation stats reset after %s.\n",
		short_time(now - reset_time));

	reset_time = now;
}

#endif /* MALLOC_STATS */

/* vi: set ts=4 sw=4 cindent:  */
