/******************************************************************************
* All mallocs from irit modules should be piped through this allocator.       *
*******************************************************************************
* (C) Gershon Elber, Technion, Israel Institute of Technology                 *
*******************************************************************************
*					 Written by Gershon Elber, April 1993 *
******************************************************************************/

#include <stdio.h>
#include <string.h>
#if defined(sgi) || defined(__WINNT__)
#include <malloc.h>
#endif /* sgi || __WINNT__ */
#include "irit_sm.h"
#include "imalloc.h"

#define OVERWRITE_STAMP_START 123456789L     /* A long */
#define OVERWRITE_STAMP_END   0xbd	     /* A byte */
#define OVERWRITE_STAMP_FREED -50964L        /* A long */
#define FREE_TABLE_SIZE 100000

#if defined(AMIGA) && defined(__SASC)
static VoidPtr __far *AllocPtrTable = NULL;
#else
static VoidPtr *AllocPtrTable = NULL;
#endif
static long
    IritDebugSearchPtr = 0;
static int
    IritDebugSearchPtrAbort = FALSE,
    WasDebugMallocReset = FALSE,
    AllocNumPtrs = 0,
    IritDebugMalloc = FALSE,
    IritDebugMallocInit = FALSE;

static void AllocError(char *Msg, VoidPtr p);

/*****************************************************************************
* DESCRIPTION:                                                               *
* Routine to print an allocation error to stderr happend when pointer p      *
* was involved.                         				     *
*                                                                            *
* PARAMETERS:                                                                *
*   Msg:         A description of the error.                                 *
*   p:           Pointer that is involved in the error.                      *
*                                                                            *
* RETURN VALUE:                                                              *
*   void                                                                     *
*****************************************************************************/
static void AllocError(char *Msg, VoidPtr p)
{
    fprintf(stderr, "Memory allocation error, %s, Ptr = 0x%lx\n",
	    Msg, (unsigned long) p);
    
#   ifdef MALLOC_DEBUG_INFO
    if (p != NULL) {
	char
	    **q = (char **) p;

	fprintf(stderr, "Allocated 0x%08x at %s:%d (%s)\n",
		(unsigned int) q, q[1], (int) q[2], q[0]);
    }
#   endif /* MALLOC_DEBUG_INFO */
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Tests for error in dynamically allocated memory, without affecting any     M
* such allocation or allocated blocks.       				     M
*   This routine may be used only if "IRIT_MALLOC" environment variable      M
* is set for debugging purposes and it obviously slows down running time.    M
*   The following tests are being made on every block allocated, and	     M
* messages are printed to stderr as needed:				     M
*									     M
* 1. Overwriting beyond the end of the allocated block.			     M
* 2. Overwriting below the beginning of the allocated block.		     M
* 3. Freeing an unallocated pointer.					     M
* 4. Freeing the same pointer twice.					     M
* 5. If "IRIT_MALLOC_PTR" environment variable is set to an address, this    M
*    address is being search for during allocation (IritMalloc) and          M
*    announced when detected.						     M
*									     M
* If this function is compiled with '#define MALLOC_DEBUG_INFO' more	     M
* information is printed regarding the allocating location (filename/line    M
* number) as well as the object type involved. This also introduce some      M
* delay into the allocation process.					     M
*                                                                            *
* PARAMETERS:                                                                M
*   PrintAllAlloc:   If TRUE, prints information all all allocated block,    M
*                    not just block with errors.			     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritFree, IritMalloc, IritRealloc, IritDebugMallocReset,		     M
*   IritDebugMallocSearchPtr, IritDebugMallocAllocated                       M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritTestAllDynMemory, allocation                                         M
*****************************************************************************/
void IritTestAllDynMemory(int PrintAllAlloc)
{
    int i;

#ifdef __WINNT__
    switch (_heapchk()) {
	case _HEAPBADBEGIN:
	    AllocError("WinNT: Initial header info. cannot be found or is bad",
		       NULL);
	    break;
	case _HEAPBADNODE:
	    AllocError("WinNT: Bad node has been found or heap is damaged",
		       NULL);
	    break;
	case _HEAPEMPTY:
	case _HEAPOK:
	default:
	    break;
    }
#endif /* __WINNT__ */

    if (AllocPtrTable == NULL) {
	fprintf(stderr, "Allocation table was not initialized (call IritMalloc once/setenv IRIT_MALLOC)\n");
    }

    for (i = 0; i < AllocNumPtrs; i++) {
	if (AllocPtrTable[i] != NULL) {
	    unsigned long size;
	    unsigned char *p2,
		*p = (unsigned char *) AllocPtrTable[i];

#	    ifdef MALLOC_DEBUG_INFO
	        unsigned char
		    *p12 = (unsigned char *) (p + 12);
	    
		size = *((unsigned long *) p12);
	        p = p12;
#	    else
		size = *((unsigned long *) p);
#	    endif /* MALLOC_DEBUG_INFO */

	    p2 = p + 8 + size;

	    if (*((long *) (p + 4)) != OVERWRITE_STAMP_START)
		AllocError("Overwritten start of dynamically allocated memory",
			   p);
	    else if (*p2 != OVERWRITE_STAMP_END)
		AllocError("Overwritten end of dynamically allocated memory",
			   p);
	    else if (PrintAllAlloc) {
#		ifdef MALLOC_DEBUG_INFO
		    char
			**q = (char **) AllocPtrTable[i];

		    fprintf(stderr, "Allocated 0x%08x at %s:%d (%s)\n",
			    (unsigned int) (p - 12), q[1], (int) q[2], q[0]);
#		else
		    fprintf(stderr, "Allocated 0x%08x\n", (unsigned int) p);
#		endif /* MALLOC_DEBUG_INFO */
	    }
	}
    }
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Routine to reallocate dynamic memory for all IRIT program/tool/libraries.  M
*    All requests for dynamic memory should invoke this function.	     M
*    If the environment variable "IRIT_MALLOC" is set when an IRIT program   M
* is executed, the consistency of the dynamic memory is tested on every      M
* invokation of this routine. See IritTestAllDynMemory function for more.    M
*                                                                            *
* PARAMETERS:                                                                M
*   p:        Old pointer to reallocate.				     M
*   Size:     Size of block to allocate, in bytes.                           M
*                                                                            *
* RETURN VALUE:                                                              M
*   VoidPtr:  A pointer to the allocated block. A function calling this      M
*             may assume return value will never be NULL, since no more      M
*             memory cases are trapped locally.				     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritFree, IritMalloc, IritTestAllDynMemory, IritDebugMallocReset,	     M
*   IritDebugMallocSearchPtr, IritDebugMallocAllocated                       M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritRealloc, allocation                                                  M
*****************************************************************************/
VoidPtr IritRealloc(VoidPtr p, unsigned Size)
{
    VoidPtr
	NewP = IritMalloc(Size);

    GEN_COPY(NewP, p, Size);

    IritFree(p);
    return NewP;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Routine to allocate dynamic memory for all IRIT program/tool/libraries.    M
*    All requests for dynamic memory should invoke this function.	     M
*    If the environment variable "IRIT_MALLOC" is set when an IRIT program   M
* is executed, the consistency of the dynamic memory is tested on every      M
* invokation of this routine. See IritTestAllDynMemory function for more.    M
*                                                                            *
* PARAMETERS:                                                                M
*   Size:     Size of block to allocate, in bytes.                           M
*   ObjType:  This variable exists iff "#define MALLOC_DEBUG_INFO".	     M
*	      This holds the object descriptions.			     M
*   FileName: This variable exists iff "#define MALLOC_DEBUG_INFO".	     M
*	      This holds the file name where the call is invoked from.	     M
*   LineNum:  This variable exists iff "#define MALLOC_DEBUG_INFO".	     M
*	      This holds the line number where the call is invoked from.     M
*                                                                            *
* RETURN VALUE:                                                              M
*   VoidPtr:  A pointer to the allocated block. A function calling this      M
*             may assume return value will never be NULL, since no more      M
*             memory cases are trapped locally.				     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritFree, IritRealloc, IritTestAllDynMemory, IritDebugMallocReset,       M
*   IritDebugMallocSearchPtr, IritDebugMallocAllocated                       M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritMalloc, allocation                                                   M
*****************************************************************************/
#ifdef MALLOC_DEBUG_INFO
#undef IritMalloc
VoidPtr IritMalloc(unsigned Size, char *ObjType, char *FileName, int LineNum)
#else
VoidPtr IritMalloc(unsigned Size)
#endif /* MALLOC_DEBUG_INFO */
{
    VoidPtr p;
    unsigned int OldSize;

    if (!IritDebugMallocInit) {
        IritDebugMalloc = getenv("IRIT_MALLOC") != NULL;
        IritDebugSearchPtr = getenv("IRIT_MALLOC_PTR") != NULL ?
					atoi(getenv("IRIT_MALLOC_PTR")) : 0;
        IritDebugSearchPtrAbort = getenv("IRIT_MALLOC_PTR_ABORT") != NULL;

#       ifdef sgi
	    mallopt(M_MXFAST, 256);
	    mallopt(M_BLKSZ, 65536);
#       endif /* sgi */

	if (IritDebugMalloc)
	    AllocPtrTable = malloc(FREE_TABLE_SIZE * sizeof(VoidPtr));

	IritDebugMallocInit = TRUE;
    }

    if (IritDebugMalloc) {
	IritTestAllDynMemory(0);

	OldSize = Size;
	Size += 16;

#ifdef MALLOC_DEBUG_INFO
	Size += 12;
#endif /* MALLOC_DEBUG_INFO */
    }

    if ((p = malloc(Size)) != NULL) {
	if (IritDebugMalloc) {
	    int i;
	    VoidPtr p2;

	    if (p != NULL && ((long) p) == IritDebugSearchPtr) {
		printf("Pointer 0x%08lx just allocated (abort = %d)\n",
		       (unsigned long) p, --IritDebugSearchPtrAbort);
		if (IritDebugSearchPtrAbort <= 0)
		    abort();
	    }

	    /* Save allocated pointer so we can search for it when freed. */
	    for (i = 0; i < AllocNumPtrs; i++) {
	        if (AllocPtrTable[i] == NULL) {
		    AllocPtrTable[i] = p;
		    break;
		}
	    }

	    if (i >= AllocNumPtrs) {
		if (i < FREE_TABLE_SIZE - 1)
		    AllocPtrTable[AllocNumPtrs++] = p;
		else {
		    fprintf(stderr, "Allocation table too small.\n");
		    exit(1);
		}
	    }

#ifdef MALLOC_DEBUG_INFO
	    /* Save the information on where this was allocated from in the */
	    /* object's first 12 bytes using three pointers.		    */
	    {
		char **q = (char **) p;

		q[0] = ObjType;
		q[1] = FileName;
		q[2] = (char *) LineNum;
		p = &q[3];
	    }
#endif /* MALLOC_DEBUG_INFO */

	    *((long *) p) = OldSize;
	    *((long *) (((char *) p) + 4)) = OVERWRITE_STAMP_START;
	    p = ((char *) p) + 8;
	    p2 = ((char *) p) + OldSize;
	    *((char *) p2) = (char) OVERWRITE_STAMP_END;

	    /* Clear out the memory allocated. */
	    ZAP_MEM(p, OldSize);
	}
	    
	return p;
    }

    IritFatalError("Not Enough dynamic memory");

    return NULL;				    /* Make warnings silent. */
}

/*****************************************************************************
* DESCRIPTION:                                                               M
* Routine to free dynamic memory for all IRIT program/tool/libraries.        M
*    All requests to free dynamic memory should invoke this function.        M
*    If the environment variable "IRIT_MALLOC" is set when an IRIT program   M
* is executed, the consistency of the dynamic memory is tested on every      M
* invokation of this routine. See IritTestAllDynMemory function for more.    M
*                                                                            *
* PARAMETERS:                                                                M
*   p:          Pointer to a block that needs to be freed.                   M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritMalloc, IritRealloc, IritTestAllDynMemory, IritDebugMallocReset,     M
*   IritDebugMallocSearchPtr, IritDebugMallocAllocated                       M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritFree, allocation                                                     M
*****************************************************************************/
void IritFree(VoidPtr p)
{
    if (IritDebugMalloc) {
	int i;

	if (*((long *) p) == OVERWRITE_STAMP_FREED)
	        AllocError("Trying to free a free object again",
#		    ifdef MALLOC_DEBUG_INFO
			   ((char *) p) - 20);
#		    else
			   ((char *) p) - 8);
#		    endif /* MALLOC_DEBUG_INFO */

	if (p == NULL)
	    AllocError("Free a NULL pointer", NULL);

	IritTestAllDynMemory(0);

	*((long *) p) = OVERWRITE_STAMP_FREED;
	p = ((char *) p) - 8;

#ifdef MALLOC_DEBUG_INFO
	p = ((char *) p) - 12;
#endif /* MALLOC_DEBUG_INFO */

        if (((long) p) == IritDebugSearchPtr) {
	    printf("Pointer 0x%08lx just released (abort = %d)\n",
		   (unsigned long) p, IritDebugSearchPtrAbort);
	    if (IritDebugSearchPtrAbort <= 0)
		abort();
	}

	/* Compare the freed pointer with the list of allocated ones. */
	for (i = 0; i < AllocNumPtrs; i++) {
	    if (AllocPtrTable[i] == p) {
		AllocPtrTable[i] = NULL;
		break;
	    }
	}

	if (i >= AllocNumPtrs && !WasDebugMallocReset)
	    AllocError("Free unallocated pointer", p);
    }

#ifdef __DEBUG_TC_MALLOC__
    switch (heapcheck()) {
	case _HEAPCORRUPT:
	    AllocError("Heap is corrupted", p);
	    break;
	case _BADNODE:
	    AllocError("Attempt to free a bogus pointer", p);
	    break;
	case _FREEENTRY:
	    AllocError("Attempt to free an already freed pointer", p);
	    break;
	case _HEAPOK:
	case _HEAPEMPTY:
	case _USEDENTRY:
	    break;
	default:
	    AllocError("Allocation error", p);
	    break;
    }
#endif /* __DEBUG_TC_MALLOC__ */

    free(p);
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Reset the malloc debugging code. This function will reset the malloc     M
* debuging code iff IritDebugMalloc is TRUE as set via "IRIT_MALLOC" env.    M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritFree, IritMalloc, IritRealloc, IritTestAllDynMemory,		     M
*   IritDebugMallocSearchPtr, IritDebugMallocAllocated                       M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritDebugMallocReset                                                     M
*****************************************************************************/
void IritDebugMallocReset(void)
{
    AllocNumPtrs = 0;

    if (!IritDebugMallocInit) {
	AllocPtrTable = malloc(FREE_TABLE_SIZE * sizeof(VoidPtr));

	IritDebugMallocInit = TRUE;
    }

    WasDebugMallocReset = TRUE;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Set the searched malloced pointer. This function will take affect iff    M
* IritDebugMalloc is TRUE as set via "IRIT_MALLOC" env.			     M
*                                                                            *
* PARAMETERS:                                                                M
*   p:		The pointer to search for.                                   M
*   Abort:	If TRUE, we also abort() once searched pointer is detected.  M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritFree, IritMalloc, IritRealloc, IritTestAllDynMemory,		     M
*   IritDebugMallocSearchPtr, IritDebugMallocAllocated      		     M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritDebugMallocReset                                                     M
*****************************************************************************/
void IritDebugMallocSearchPtr(VoidPtr p, int Abort)
{
    IritDebugSearchPtr = (long) p;
    IritDebugSearchPtrAbort = Abort;
}

/*****************************************************************************
* DESCRIPTION:                                                               M
*   Prints the currently allocated memory blocks. This function will take    M
* affect iff IritDebugMalloc is TRUE as set via "IRIT_MALLOC" env.           M
*                                                                            *
* PARAMETERS:                                                                M
*   None                                                                     M
*                                                                            *
* RETURN VALUE:                                                              M
*   void                                                                     M
*                                                                            *
* SEE ALSO:                                                                  M
*   IritFree, IritMalloc, IritRealloc, IritTestAllDynMemory,		     M
*   IritDebugMallocReset, IritDebugMallocSearchPtr                           M
*                                                                            *
* KEYWORDS:                                                                  M
*   IritDebugMallocReset                                                     M
*****************************************************************************/
void IritDebugMallocAllocated(void)
{
    IritTestAllDynMemory(TRUE);
}
