/*
    memory.c

    UltraRogue: The Ultimate Adventure in the Dungeons of Doom
    Copyright (C) 1995 Herb Chong
    All rights reserved.

    See the file LICENSE.TXT for full copyright and licensing information.
*/

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

#include "dict.h"
#include "memory.h"
#include "rogue.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

static char sccsid[] = "%W%\t%G%";

/*	Debugging memory allocation code that tries to trap common memory problems
	like overwriting storage and stepping on memory pointer chains. If code
	doesn't use malloc, free, and realloc a lot, these routines can be left in
	as added protection against undetected storage bugs.
*/

/* 	FENCE_SIZE should be a multiple of sizeof(size_t) to prevent alignment problems.
	The code assumes that malloc and realloc return pointers aligned at least on size_t
	sized boundaries and that a pointer needs alignment no more strict than that of an
	object needed to hold a size_t.
*/

#define FENCE_SIZE (sizeof(size_t) * 1024)

static int memdebug_level = 0;
static DICTIONARY *allocations = NULL;
static FILE *trace_file = NULL;

/* set the debug level */
void mem_debug(const int level)
{
	memdebug_level = level;

#ifdef MEM_DEBUG
	if (trace_file == NULL)
		trace_file = fopen("trace", "w");

	/* all except 0, 1, and unknown fall through */
	switch(memdebug_level) {
	case 2:
		fprintf(trace_file, "+++ Memory tracking possible, ");
	case 1:
		fprintf(trace_file, "+++ Memory debugging enabled, ");
		break;
	case 0:
		fprintf(trace_file, "+++ Memory debugging disabled, ");
		break;
	default:
		fprintf(trace_file, "!!! Unknown memory debug level set, enabling level 1, ");
		memdebug_level = 1;
		break;
	}
	fprintf(trace_file, "fence size = %d\n", FENCE_SIZE);
#endif
}

/* set memory tracking on or off */
/* turning it off deletes all tracking data */
void mem_tracking(int flag)
{
#ifdef MEM_DEBUG
	/* do nothing if debuglevel is too low */
	if (memdebug_level < 2)
		return;

	/* turn on tracking */
	if (flag > 0) {
		if (allocations != NULL) {
			dict_destroy(allocations);
			allocations = NULL;
		}
		allocations = dict_create(8, 100, 4, 20);
		if (allocations == NULL) {
			fprintf(trace_file, "!!! Unable to allocate tracking table!\n");
			abort();
		}
	}
	/* turn off tracking */
	else if (allocations != NULL) {
		dict_destroy(allocations);
		allocations = NULL;
	}
#endif
}

/* go through all pointers and see if they are OK, aborting if not */
/* always returns 1 if not aborting so that it can be included in  */
/* if statement boolean expressions */
int mem_check(char *fname, int linenum)
{
#ifdef MEM_DEBUG
	STRING_ENTRY *se;

	/* scan of a NULL dictionary always succeeds */
	if (allocations == NULL)
		return TRUE;

	if (!dict_scan_begin(allocations)) {
		fprintf(trace_file, "!!! Dictionary scan initialization failed!\n");
		abort();
	}

	fprintf(trace_file, "\n+++ --- Starting pointer scan\n");
	fprintf(trace_file, "+++ --- At %s, %d\n", fname, linenum);

	/* mem_validate aborts if there is a problem */
	while((se = dict_scan_next(allocations)) != NULL)
		mem_validate(se->any_ptr);

	fprintf(trace_file, "+++ --- Done pointer scan\n\n");

#endif
	/* always return a good value if execution arrives here */
	return 1;
}

/* allocate some memory and initialize header and trailer */
void *mem_malloc(const size_t bytes)
{
#ifdef MEM_DEBUG
	char *mem_temp;
	size_t real_size = bytes + (FENCE_SIZE << 1);

	/* allocate including guard bytes to detect some ways of overwriting of memory areas */
	mem_temp = (void *)malloc(real_size);
	if (memdebug_level > 0) {
		fprintf(trace_file, "+++ Requested size of %ld bytes\n", bytes);
		fprintf(trace_file, "+++ Actual malloc of %ld bytes located at %p\n", real_size, mem_temp);
	}

	/* if allocation succeeded, set management data */
	if (mem_temp != NULL) {
		size_t i;
		char *end;

		/* do beginning marker bytes */
		for (i = 0; i < FENCE_SIZE - sizeof(size_t); i++)
			*mem_temp++ = 145;

		/* save size in header too */
		if (memdebug_level > 0)
			fprintf(trace_file, "*** Requested memory size stored at %p\n", mem_temp);
		*(size_t *)mem_temp = bytes;

		/* finally, point to storage we are going to hand out */
		mem_temp += sizeof(size_t);

		/* now, point to trailer bytes and do them */
		end = mem_temp + bytes;
		for (i = 0; i < FENCE_SIZE; i++)
			*end++ = 145;

		/* now zap contents to zero */
		for (i = 0; i < bytes; i++)
			mem_temp[i] = 0;
	}

	/* track pointer if needed */
	if (memdebug_level > 1 && allocations != NULL) {
		char key[16];
		long temp;

		sprintf(key, "%p", mem_temp);
		if (dict_insert(allocations, key, 1, (const unsigned long) bytes, mem_temp, &temp) == NULL) {
			fprintf(trace_file, "!!! Insert of pointer tracking info failed\n");
			abort();
		}
	}

	/* allow caller to do error handling */
	if (memdebug_level > 0) {
		fprintf(trace_file, "--- Returning pointer of %p\n", mem_temp);
		fflush(trace_file);
	}
	return (void *)mem_temp;
#else
	return malloc(bytes);
#endif
}

/* release some memory, making sure that it was properly allocated */
void mem_free(const void *ptr)
{
#ifdef MEM_DEBUG
	char *mem_temp;
	size_t mem_size;
	size_t i;

	if (memdebug_level > 0)
		fprintf(trace_file, "+++ Free of memory located at %p\n", ptr);
	if (ptr == NULL) {
		if (memdebug_level > 0) {
			fprintf(trace_file, "!!! Freeing NULL pointer\n");
			fflush(trace_file);
		}
		abort();
	}

	mem_validate(ptr);	/* doesn't return on error */

	/* get location of size of area */
	mem_temp = (char *)ptr - sizeof(size_t);

	/* get and calculate real size */
	mem_size = *(size_t *)mem_temp + (FENCE_SIZE << 1);

	/* if doing memory tracking */
	if (memdebug_level > 1 && allocations != NULL) {
		char key[16];
		STRING_ENTRY *se;
		long temp;

		sprintf(key, "%p", ptr);

		if ((se = dict_search(allocations, key, &temp)) == NULL) {
			fprintf(trace_file, "!!! Deleting pointer not found in tracking info\n");
			abort();
		}

		if (se->count == 0) {
			fprintf(trace_file, "!!! Freeing a pointer that has already been freed!\n");
			abort();
		}
		else if (se->flags != mem_size - (FENCE_SIZE << 1)) {
			fprintf(trace_file, "!!! Stored size different from tracking size!\n");
			abort();
		}

		/* remember deleted stuff by zeroing the allocation count */
		se->count = 0;
		se->flags = 0;
	}

	/* zap bytes being freed */
	for (i = 0, mem_temp = (char *)ptr - FENCE_SIZE; i < mem_size; i++, mem_temp++)
		*mem_temp = 243;

	if (memdebug_level > 0)
		fflush(trace_file);

	mem_temp = (char *)ptr - FENCE_SIZE;
	free((void *)mem_temp);
#else
	free((void *) ptr);
#endif
}

/* reallocate some memory, making sure that it was properly allocated */
void *mem_realloc(const void *ptr, const size_t new_size)
{
#ifdef MEM_DEBUG
	char *mem_temp = (char *)ptr;
	size_t real_size = new_size + (FENCE_SIZE << 1);
	size_t mem_size;
	long i;

	if (memdebug_level > 0) {
		fprintf(trace_file, "+++ Requested size of %ld bytes\n", new_size);
		fprintf(trace_file, "+++ Actual realloc of %ld bytes located at %p\n", real_size, mem_temp);
	}
	if (ptr == NULL) {
		if (memdebug_level > 0) {
			fprintf(trace_file, "!!! Reallocating NULL pointer\n");
			fflush(trace_file);
		}
		abort();
	}

	mem_validate(ptr);	/* doesn't return on error */

	/* if doing memory tracking */
	if (memdebug_level > 1 && allocations != NULL) {
		char key[16];
		STRING_ENTRY *se;
		long temp;

		sprintf(key, "%p", ptr);

		if ((se = dict_search(allocations, key, &temp)) == NULL) {
			fprintf(trace_file, "!!! Deleting a pointer not found in tracking info!\n");
			abort();
		}

		/* point to size bytes */
		mem_temp = (char *)ptr - sizeof(size_t);

		/* get user size */
		mem_size = *(size_t *)mem_temp;

		if (se->count == 0) {
			fprintf(trace_file, "!!! Freeing a pointer that has already been freed!\n");
			abort();
		}
		else if (se->flags != mem_size) {
			fprintf(trace_file, "!!! Stored size different from tracking size!\n");
			abort();
		}

		/* remember deleted stuff by zeroing the allocation count */
		se->count = 0;
		se->flags = 0;
	}


	/* header marker bytes will be copied by the realloc */
	mem_temp = (char *)ptr - FENCE_SIZE;
	mem_temp = realloc((void *)mem_temp, real_size);

	if (mem_temp != NULL) {
		char *end;

		/* save size in header too */
		mem_temp += FENCE_SIZE - sizeof(size_t);
		if (memdebug_level > 0)
			fprintf(trace_file, "*** Requested memory size stored at %p\n", mem_temp);
		*(size_t *)mem_temp = new_size;

		/* finally, point to storage we are going to hand out */
		mem_temp += sizeof(size_t);

		/* now, point to trailer bytes and do them */
		end = mem_temp + new_size;
		for (i = 0; i < FENCE_SIZE; i++)
			*end++ = 145;
	}

	if (memdebug_level > 1 && allocations != NULL) {
		char key[16];
		long temp;

		sprintf(key, "%p", mem_temp);
		if (dict_insert(allocations, key, 1, (const unsigned long)new_size, mem_temp, &temp) == NULL) {
			fprintf(trace_file, "!!! Insert of pointer tracking info failed\n");
			abort();
		}
	}

	if (memdebug_level > 0) {
		fprintf(trace_file, "--- Returning pointer of %p\n", mem_temp);
		fflush(trace_file);
	}
	return (void *)mem_temp;
#else
	return realloc((void *) ptr, new_size);
#endif
}

/* check a pointer to be sure all check bytes are OK. abort if not */
/* always returns 1 if not aborting so that it can be included in  */
/* if statement boolean expressions */
int mem_validate(const void *ptr)
{
#ifdef MEM_DEBUG
	unsigned char *mem_temp = (unsigned char *)ptr;
	size_t mem_size;
	size_t i;

	/* NULL pointers are always valid */
	if (ptr == NULL)
		return 1;

	if (memdebug_level > 0)
		fprintf(trace_file, "+++ Checking %p as pointer\n", ptr);


	if (memdebug_level > 1 && allocations != NULL) {
		char key[16];
		STRING_ENTRY *se;
		long temp;

		sprintf(key, "%p", ptr);

		if ((se = dict_search(allocations, key, &temp)) == NULL) {
			fprintf(trace_file, "!!! Pointer not found in tracking info!\n");
			abort();
		}

		/* point to size bytes */
		mem_temp = (unsigned char *)ptr - sizeof(size_t);

		/* get user size */
		mem_size = *(size_t *)mem_temp;

		if (se->count == 0) {
			fprintf(trace_file, "!!! Checking pointer has been freed!\n");
			abort();
		}
		else if (se->flags != mem_size) {
			fprintf(trace_file, "!!! Stored size different from tracking size!\n");
			abort();
		}
	}

	/* check the header bytes */
	mem_temp = (unsigned char *) ptr - FENCE_SIZE;
	if (memdebug_level > 0)
		fprintf(trace_file, "+++ Real pointer at %p\n", mem_temp);


	for (i = 0; i < FENCE_SIZE - sizeof(size_t); i++)
		if (*mem_temp++ != 145) {
			if (memdebug_level > 0) {
				fprintf(trace_file, "!!! The user pointer at %p has been overwritten\n", ptr);
				fprintf(trace_file, "!!! Header offset %ld has been changed\n", i - 1);
				fflush(trace_file);
			}
			abort();
		}

	/* check size */
	i = *(size_t *)mem_temp;
	if (memdebug_level > 0)
		fprintf(trace_file, "*** Stored memory size of %ld bytes in header\n", i);


	/* now point to where trailer should be */
	mem_temp = (unsigned char *)ptr + i;
	for (i = 0; i < FENCE_SIZE; i++)
		if (*mem_temp++ != 145) {
			if (memdebug_level > 0) {
				fprintf(trace_file, "!!! The user pointer at %p has been overwritten\n", ptr);
				fprintf(trace_file, "!!! Trailer offset %ld has been changed\n", i - 1);
				fflush(trace_file);
			}
			abort();
		}
	if (memdebug_level > 0)
		fflush(trace_file);
#endif
	return 1;
}
