/*	This file is part of the checked memory manager MALLOC.
	Written by Dick Grune, Vrije Universiteit, Amsterdam.
	$Id: Malloc.c,v 1.24 2017-01-24 19:46:57 Gebruiker Exp $
*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<string.h>

#include	"any_int.h"
#include	"Malloc.h"
/* make malloc.h available */
#undef	malloc
#undef	calloc
#undef	realloc
#undef	free


/*Library module source prelude */
#undef	_MALLOC_CODE_
#ifndef	lint
#define	_MALLOC_CODE_
#endif
#ifdef	LIB
#define	_MALLOC_CODE_
#endif

#ifdef	_MALLOC_CODE_

/* Library module source code */

#undef	new
#define	new		use_my_new	/* don't call Malloc in Malloc.c */
#define	my_new(type)	((type *)malloc(sizeof (type)))

/* All output goes through designated files, so we block printf, etc. */
#undef	printf
#define	printf	use_fprintf
#undef	putchar
#define	putchar	use_fprintf

static size_t restricted_balance = 0;	/* to simulate out-of-memory */

static void
fprintloc(FILE *out, const char *fname, int l_nmb) {
	fprintf(out, "\"%s\", line %d: ", fname, l_nmb);
}

void
_out_of_memory(const char *msg, const char *fname, int l_nmb, size_t size) {
	fprintloc(stderr, fname, l_nmb);
	fprintf(stderr, "OUT OF MEMORY");
	if (msg) {
		fprintf(stderr, ": %s", msg);
	}
	if (size != 0) {
		fprintf(stderr, ", requested size = %s bytes",
			any_uint2string(size, 0));
	}
	fprintf(stderr, "\n");
	fflush(stderr);
	ReportMemoryStatus(stderr);
	exit(1);
}


							/* ADMINISTRATION */
static vlong_uint total = 0;
static vlong_uint balance = 0;
static vlong_uint max = 0;

struct alloc {	/* corresponds to an allocated block */
	struct alloc *next;
	const char *addr;
	size_t size;
	const char *fname;
	int l_nmb;
};

#define	HASH_SIZE	16381		/* largest prime under 2^16 */
static struct alloc *alloc_bucket[HASH_SIZE];
#define	alloc_bucket_for(x)	alloc_bucket[((unsigned int)(x)%HASH_SIZE)]

static void
register_alloc(char *addr, size_t size, const char *fname, int l_nmb) {
	/* registers the allocation of a block in the administration */
	struct alloc *new;
	struct alloc **al_hook = &alloc_bucket_for(addr);

	if (addr == 0) return;

	new = my_new(struct alloc);
	new->addr = addr;
	new->size = size;
	new->fname = fname;		/* no need to copy fname */
	new->l_nmb = l_nmb;
	new->next = *al_hook;
	*al_hook = new;

	total += size;
	balance += size;
	if (balance > max) {
		max = balance;
	}
}


static struct alloc **
pointer_to_alloc_for(const char *addr) {
	struct alloc **al_hook = &alloc_bucket_for(addr);

	while (*al_hook) {
		if ((*al_hook)->addr == addr) break;
		al_hook = &(*al_hook)->next;
	}

	return al_hook;
}

static size_t
register_free(char *addr) {
	/* registers the freeing of a block */
	struct alloc **old_p = pointer_to_alloc_for(addr);
	struct alloc *old = *old_p;

	if (old == 0) return (size_t) -1;
	size_t old_size = old->size;

	*old_p = old->next;
	free((void *)old);

	balance -= old_size;
	return old_size;
}

void
MemClobber(void *p, size_t size) {
	unsigned char *s = (unsigned char *)p;
	size_t i;

	for (i = 0; i < size; i++) {
		s[i] = 0125;		/* 0101 0101 */
	}
}

							/* MEMORY STATUS */
struct call {	/* summarizes all the allocations at a call in the program */
	struct call *next;
	const char *fname;
	int l_nmb;
	unsigned int n_blocks;
	int var_size;	/* all blocks have the same size or not */
	size_t size;	/* !var_size: the one size; var_size: sum of sizes */
};

static struct call *
compacted_calls(void) {
	struct call *list_of_calls = 0;
	int i;

	for (i = 0; i < HASH_SIZE; i++) {
		struct alloc *al = alloc_bucket[i];

		while (al) {
			struct call *cl = list_of_calls;

			/* try to find a call entry for this program location */
			while (cl) {
				if (	cl->fname == al->fname
				&&	cl->l_nmb == al->l_nmb
				)	break;
				cl = cl->next;
			}

			if (cl) {
				/* this is known call; update */
				if (cl->var_size) {
					cl->size += al->size;
				}
				else if (cl->size != al->size) {
					/* switch to var_size */
					cl->var_size = 1;
					cl->size =
					    cl->n_blocks*cl->size + al->size;
				}
				cl->n_blocks++;
			}
			else {	/* this is a new call */
				cl = my_new(struct call);
				cl->fname = al->fname;
				cl->l_nmb = al->l_nmb;
				cl->n_blocks = 1;
				cl->var_size = 0;
				cl->size = al->size;

				/* prepend to list_of_calls */
				cl->next = list_of_calls;
				list_of_calls = cl;
			}

			al = al->next;
		}
	}

	return list_of_calls;
}

static int
number_of_calls(const struct call *cl) {
	int res = 0;

	while (cl != 0) {
		res++;
		cl = cl->next;
	}

	return res;
}

static void
report_actual_call(FILE *out, const struct call *cl) {
	fprintloc(out, cl->fname, cl->l_nmb);
	fprintf(out, "still allocated: %d block%s of size ",
		cl->n_blocks, (cl->n_blocks == 1 ? "" : "s")
	);
	if (cl->var_size) {
		/* cl->size is the sum of the sizes */
		size_t av = (cl->size+cl->n_blocks/2) / cl->n_blocks;
		fprintf(out, "%s on average", any_uint2string(av, 0));
		if (cl->n_blocks > 1) {
			fprintf(out, " = %s", any_uint2string(cl->size, 0));
		}
	}
	else {
		/* cl->size is the single size */
		fprintf(out, "%s", any_uint2string(cl->size, 0));
		if (cl->n_blocks > 1) {
			vlong_uint all = cl->size*cl->n_blocks;
			fprintf(out, " = %s", any_uint2string(all, 0));
		}
	}
	fprintf(out, "\n");
}

static void
report_actual_calls(FILE *out) {
	const struct call *cl = compacted_calls();	/* allocates cl */
	int n_calls = number_of_calls(cl);

	if (n_calls == 0) return;

	fprintf(out, "There %s %d call position%s with unreclaimed memory:\n",
		(n_calls == 1 ? "is" : "are"),
		n_calls,
		(n_calls == 1 ? "" : "s")
	);

	while (cl) {
		report_actual_call(out, cl);
		struct call *next_cl = cl->next;
		free((void *)cl);			/* frees cl */
		cl = next_cl;
	}
}

void
ReportMemoryStatus(FILE *out) {
	if (out == 0) out = stderr;
	report_actual_calls(out);

	fprintf(out, "Total memory allocated = %s", any_uint2string(total, 0));
	fprintf(out, ", max. allocated = %s", any_uint2string(max, 0));
	fprintf(out, ", still allocated = %s", any_uint2string(balance, 0));
	fprintf(out, "\n");
	fflush(out);
}

							/* MALLOC */
void *
_mreg_malloc(int chk, size_t size, const char *fname, int l_nmb) {
	void *res;

	if (restricted_balance && balance + size > restricted_balance) {
		res = 0;
	} else {
		res = malloc(size);
	}

	if (res == 0) {
		if (chk) {
			_out_of_memory(0, fname, l_nmb, size);
			/*NOTREACHED*/
		}
		return res;
	}

	register_alloc(res, size, fname, l_nmb);

#ifdef	MEMCLOBBER
	MemClobber((char *)res, size);
#endif	/* MEMCLOBBER */

	return res;
}

void *
_mreg_calloc(int chk, size_t n, size_t size, const char *fname, int l_nmb) {
	void *res;

	if (restricted_balance && balance + n*size > restricted_balance) {
		res = 0;
	} else {
		res = calloc(n, size);
	}

	if (res == 0) {
		if (chk) {
			_out_of_memory(0, fname, l_nmb, n*size);
			/*NOTREACHED*/
		}
		return res;
	}

	register_alloc(res, n*size, fname, l_nmb);

	return res;
}

void *
_mreg_realloc(int chk, void *addr, size_t size, const char *fname, int l_nmb) {
	void *res;
	size_t old_size = register_free(addr);

	/* we report first, because the realloc() below may cause a crash */
	if (	/* we are not reallocating address 0, which is allowed */
		addr != 0
	&&	/* the address was never handed out before */
		old_size == (size_t) -1
	) {
		fprintloc(stderr, fname, l_nmb);
		fprintf(stderr, ">>>> unallocated block reallocated <<<<\n");
		fflush(stderr);
	}

	if (restricted_balance && balance + size > restricted_balance) {
		res = 0;
	} else {
		res = realloc(addr, size);
	}

	if (res == 0) {
		if (chk) {
			_out_of_memory(0, fname, l_nmb, size);
			/*NOTREACHED*/
		}
		return res;
	}

	register_alloc(res, size, fname, l_nmb);

#ifdef	MEMCLOBBER
	if (old_size > 0 && size > old_size) {
		MemClobber(((char *)res)+old_size, size-old_size);
	}
#endif	/* MEMCLOBBER */

	return res;
}

/* ARGSUSED */
void
_mreg_free(void *addr, const char *fname, int l_nmb) {
	size_t old_size = register_free(addr);

	/* we report first, because the free() below may cause a crash */
	if (old_size == (size_t) -1) {
		fprintloc(stderr, fname, l_nmb);
		fprintf(stderr, ">>>> unallocated block freed ");
		fprintf(stderr, "or multiple free of allocated block <<<<\n");
		fflush(stderr);
	}
	else {
#ifdef	MEMCLOBBER
	MemClobber((char *)addr, old_size);
#endif	/* MEMCLOBBER */
	}
	free(addr);
}

char *
_new_string(int chk, const char *s, const char *fname, int l_nmb) {
	return strcpy((char *)(_mreg_malloc(chk, strlen(s)+1, fname, l_nmb)),
		      s);
}

/* End library module source code */
#endif	/* _MALLOC_CODE_ */

#ifdef	lint
static void
satisfy_lint(void *x) {
	void *v;

	v = _mreg_malloc(0, 0, 0, 0);
	v = _mreg_calloc(0, 0, 0, 0, 0);
	v = _mreg_realloc(0, 0, 0, 0, 0);
	_mreg_free(x, 0, 0);

	OutOfMemoryExit(0);
	ReportMemoryStatus(0);
	MemClobber(v, 0);

	v = _new_string(0, 0, 0, 0);
	satisfy_lint(v);
}
#endif	/* lint */
