/*===========================================================================
 *
 * File:	DL_Block.CPP
 * Author:	Dave Humphrey (uesp@m0use.net)
 * Created On:	Tuesday, April 03, 2001
 *
 * Implements the memory logging routines for debug builds.
 * If allocated memory blocks are stored in the block info structure 
 * upon creation and removed upon deletion, the logging routines
 * can trace memory blocks that are deleted more than once or blocks that
 * are never freed.
 *
 * The code is primarily from "Writing Solid Code" by Steve Maguire.
 *
 *=========================================================================*/
#if defined(_DEBUG)

	/* Include Files */
#include "dl_block.h"
#include "dl_log.h"
#include <string.h>


/*===========================================================================
 *
 * Begin Local Variables
 *
 *=========================================================================*/
  DEFINE_FILE();
/*===========================================================================
 *		End of Local Variables
 *=========================================================================*/


/*===========================================================================
 *
 * Begin Pointer Comparison Macros
 *
 * The functions in this file must compare arbitrary pointers (ie, they
 * could point to anything), which is not always portable according to ANSI.
 * Thus, the following macros are used to define pointer comparisons to
 * allow the correct comparisons to be made across various platforms.
 * Ensure the comparisons are correct for your implemenation.
 *
 *=========================================================================*/
  #define IsPtrLess(pLeft, pRight)      ((pLeft) <  (pRight))
  #define IsPtrGreater(pLeft, pRight)   ((pLeft) >  (pRight))
  #define IsPtrEqual(pLeft, pRight)     ((pLeft) == (pRight))
  #define IsPtrLessEq(pLeft, pRight)    ((pLeft) <= (pRight))
  #define IsPtrGreaterEq(pLeft, pRight) ((pLeft) >= (pRight))
/*===========================================================================
 *		End of Pointer Comparison Macros
 *=========================================================================*/


/*===========================================================================
 *
 * Begin Private File Variables
 *
 *=========================================================================*/

	/* Points to the start of a singly linked list of memory block
	 * information structures */
  static blockinfo_t* pBIHead = NULL;	

/*===========================================================================
 *		End of Private File Variables
 *=========================================================================*/


/*===========================================================================
 *
 * Function - static blockinfo_t* GetBlockInfo (pBlock);
 *
 * A local function which attempts to return the block information structure
 * for the given memory block.  If the block does not exist, the function
 * will ASSERT.  The function always returns a valid block.
 *
 *=========================================================================*/
static blockinfo_t* GetBlockInfo (void* pBlock) {  
  DEFINE_FUNCTION("GetBlockInfo()");
  blockinfo_t* pBlockInfo;
  byte*	       pByteBlock = (byte *) pBlock;

	/* Parse through the linked list for a match */
  for (pBlockInfo = pBIHead; pBlockInfo != NULL; pBlockInfo = pBlockInfo->pNext) {
    byte* pStart = pBlockInfo->pPointer;
    byte* pEnd   = pBlockInfo->pPointer + pBlockInfo->Size - 1;

    if (IsPtrGreaterEq(pByteBlock, pStart) && IsPtrLessEq(pByteBlock, pEnd)) break;
   }

	/* Ensure a valid block was found */
  ASSERT(pBlockInfo != NULL);
  return (pBlockInfo);
 }
/*===========================================================================
 *		End of Function blockinfo_t* GetBlockInfo()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void CheckMemoryRefs (void);
 *
 * Checks the reference member of all memory block logs looking for blocks
 * that have not been referenced with NoteMemoryRef().  The function
 * ASSERTs if any unflagged block is found.  Use this function to check
 * for dangling pointers in your application. 
 *
 *=========================================================================*/
void CheckMemoryRefs (void) {
  DEFINE_FUNCTION("CheckMemoryRefs()");
  blockinfo_t* pBlockInfo;

  for (pBlockInfo = pBIHead; pBlockInfo != NULL; pBlockInfo = pBlockInfo->pNext) {
		/* Simple check for the integrity of the block.  If this ASSERT
		 * fires it indicates that the blockinfo code has caused an
		 * error or its memory has been trashed. */
    ASSERT(pBlockInfo->pPointer != NULL && pBlockInfo->Size != 0);

		/* Ensure that the block has been referenced by a call to
		 * NoteMemoryRef(). If this ASSERT fires it indicates either
		 * a dangling pointer or that not all pointers have been
		 * accounted for. */
    ASSERT(pBlockInfo->Referenced);
   }

 }
/*===========================================================================
 *		End of Function CheckMemoryRefs()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void ClearMemoryRefs (void);
 *
 * Clears all the reference flags of the current memory block logs.  
 * Use this function before NoteMemoryRefs() to check for dangling pointers
 * in your application.
 *
 *=========================================================================*/
void ClearMemoryRefs (void) {
  //DEFINE_FUNCTION("ClearMemoryRefs()");
  blockinfo_t* pBlockInfo;

  for (pBlockInfo = pBIHead; pBlockInfo != NULL; pBlockInfo = pBlockInfo->pNext) {
    pBlockInfo->Referenced = FALSE;
   }

 }
/*===========================================================================
 *		End of Function ClearMemoryRefs()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean CreateBlockInfo (pNewBlock, NewSize);
 *
 * Creates a new memory block information structure for the given memory.
 * Returns TRUE on success or FALSE if the new block log could not be
 * created.  On failure the newly allocated block should be freed.
 *
 *=========================================================================*/
boolean CreateBlockInfo (void* pNewBlock, const size_t NewSize) {
  DEFINE_FUNCTION("CreateBlockInfo(void*, size_t)");
  blockinfo_t* pBlockInfo;

	/* Ensure valid input */
  ASSERT(pNewBlock != NULL && NewSize != 0);

	/* Create the memory log structure */
  pBlockInfo = new blockinfo_t;
  if (pBlockInfo == NULL) return (FALSE);

	/* Initialize the new memory block */
  pBlockInfo->pPointer = (byte*) pNewBlock;
  pBlockInfo->Size = NewSize;
  pBlockInfo->Referenced = TRUE;
  pBlockInfo->pNext = pBIHead;
  pBlockInfo->pName = NULL;
  pBIHead = pBlockInfo;
  return (TRUE);
 }
/*===========================================================================
 *		End of Function CreateBlockInfo()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean CreateBlockInfo (pNewBlock, NewSize, pName);
 *
 * Creates a new memory block information structure for the given memory.
 * Returns TRUE on success or FALSE if the new block log could not be
 * created.  On failure the newly allocated block should be freed.  
 * Accepts the given valid name string for thje pointer name.  ASSERTs if
 * the input pNname pointer is not valid.
 *
 *=========================================================================*/
boolean CreateBlockInfo (void* pNewBlock, const size_t NewSize, const char* pName) {
  DEFINE_FUNCTION("CreateBlockInfo(void*, size_t, char*)");
  blockinfo_t* pBlockInfo;

	/* Ensure valid input */
  ASSERT(pNewBlock != NULL && NewSize != 0 && pName != NULL);

	/* Create the memory log structure */
  pBlockInfo = new blockinfo_t;
  if (pBlockInfo == NULL) return (FALSE);

	/* Initialize the new memory block */
  pBlockInfo->pPointer = (byte*) pNewBlock;
  pBlockInfo->Size = NewSize;
  pBlockInfo->Referenced = TRUE;
  pBlockInfo->pNext = pBIHead;

	/* Attempt to create the name string */
  pBlockInfo->pName = new char[strlen(pName)+1];

  if (pBlockInfo->pName == NULL) {
    memset (pBlockInfo, (int)GARBAGE_CHAR, sizeof(blockinfo_t));
    delete pBlockInfo;
    return (FALSE);
   }
 
  strcpy (pBlockInfo->pName, pName);

  pBIHead = pBlockInfo;
  return (TRUE);
 }
/*===========================================================================
 *		End of Function CreateBlockInfo()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void FreeBlockInfo (void* pBlock)
 *
 * Attempts to remove the block info for the given memory.  The function
 * will ASSERT if given an invalid memory block.
 *
 *=========================================================================*/
void FreeBlockInfo (void* pBlock) {
  DEFINE_FUNCTION("FreeBlockInfo()");
  blockinfo_t* pBlockInfo;
  blockinfo_t* pPrevInfo = NULL;
  byte*	       pByteBlock = (byte *) pBlock;

	/* Ensure valid input */
  ASSERT(pBlock != NULL);

	/* Search the block linked list for a match, from head to tail */
  for (pBlockInfo = pBIHead; pBlockInfo != NULL; pBlockInfo = pBlockInfo->pNext) {

		/* Check for a block match */
    if (IsPtrEqual(pBlockInfo->pPointer, pByteBlock)) {

		/* Rearrange the singly linked list */
      if (pPrevInfo == NULL)
        pBIHead = pBlockInfo->pNext;
      else
        pPrevInfo->pNext = pBlockInfo->pNext;

      break;
     }

    pPrevInfo = pBlockInfo;
   } 

	/* Ensure a valid block was found to be deleted */
   ASSERT(pBlockInfo != NULL);

	/* Delete the pointer name, if any */
  if (pBlockInfo->pName != NULL) {
    memset(pBlockInfo->pName, (int)GARBAGE_CHAR, strlen(pBlockInfo->pName) + 1);
    delete[] pBlockInfo->pName;
   }

	/* Clear the blockinfo structure and delete */
  memset(pBlockInfo, (int)GARBAGE_CHAR, sizeof(blockinfo_t));
  delete pBlockInfo;
 }
/*===========================================================================
 *		End of Function FreeBlockInfo()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - size_t GetNumBlocks (void);
 *
 * Returns the total number of allocated blocks.
 *
 *=========================================================================*/
size_t GetNumBlocks (void) {
  //DEFINE_FUNCTION("GetNumBlocks()");
  blockinfo_t* pBlockInfo;
  size_t       Counter;

	/* Does the block info list have a head? */
  if (pBIHead == NULL) return (0);
  pBlockInfo = pBIHead;
  Counter = 1;

  while (pBlockInfo->pNext != NULL) {
    Counter++;
    pBlockInfo = pBlockInfo->pNext;
   }

  return (Counter);
 }
/*===========================================================================
 *		End of Function GetNumBlocks()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean IsValidPointer (pBlock, MinSize);
 *
 * Ensures that the given block is a valid memory block and that it is
 * at least MinSize bytes in size (from the given pointer to the
 * end of the block).  The passed block does not have to be the very start
 * of an allocated block, but can be anywhere in the block.
 *
 * The function will ASSERT if the block pointer or size is invalid, and
 * never actually returns NULL.  The value returned is to allow the
 * function to be used in an ASSERT statement easily, such as
 *		ASSERT(IsValidPointer(pSomeObject, size));
  *
 *=========================================================================*/
boolean IsValidPointer (void* pBlock, const size_t MinSize) {
  DEFINE_FUNCTION("IsValidPointer(void*, size_t)");
  blockinfo_t* pBlockInfo;
  byte*	       pByteBlock = (byte*) pBlock;

	/* Ensure valid input */
  ASSERT(pBlock != NULL && MinSize != 0);
  pBlockInfo = GetBlockInfo(pBlock);

	/* Ensure that the block size is valid */
  ASSERT(IsPtrLessEq(pByteBlock + MinSize, pBlockInfo->pPointer + pBlockInfo->Size));

  return (TRUE);
 }
/*===========================================================================
 *		End of Function IsValidPointer()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean IsValidPointer (pBlock);
 *
 * Same as IsValidPointer(void*, size_t) except that it just checks if
 * the given pointer is valid without requiring a minimum size.
 *
 * The function will ASSERT if the block pointer or size is invalid, and
 * never actually returns NULL.  The value returned is to allow the
 * function to be used in an ASSERT statement easily, such as
 *		ASSERT(IsValidPointer(pSomeObject));
  *
 *=========================================================================*/
boolean IsValidPointer (void* pBlock) {
  DEFINE_FUNCTION("IsValidPointer(void*)");
  blockinfo_t* pBlockInfo;

	/* Ensure valid input */
  ASSERT(pBlock != NULL);

	/* Attempt to get the block info */
  pBlockInfo = GetBlockInfo(pBlock);
  if (pBlockInfo == NULL) return (FALSE);
  return (TRUE);
 }
/*===========================================================================
 *		End of Function IsValidPointer()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void NoteMemoryRef (pBlock);
 *
 * Sets the reference flag for the log associated with the given memory
 * block.  The function ASSERTs if the given block is not valid.  Note that
 * the input block does not have to be the start of the allocated block,
 * but anywhere within the block.
 *
 *=========================================================================*/
void NoteMemoryRef (void* pBlock) {
  //DEFINE_FUNCTION("NoteMemoryRef()");
  blockinfo_t* pBlockInfo;

  pBlockInfo = GetBlockInfo(pBlock);
  pBlockInfo->Referenced = TRUE;
 }
/*===========================================================================
 *		End of Function NoteMemoryRef()
 *=========================================================================*/


#undef  __FUNC__
#define __FUNC__ "OutputBlockInfo()"
/*===========================================================================
 *
 * Function - void OutputBlockInfo (void);
 *
 * Outputs the current block information to the system log file.
 *
 *=========================================================================*/
void OutputBlockInfo (void) {
  //DEFINE_FUNCTION("OutputBlockInfo()");
  blockinfo_t* pBlockInfo;
  long         Index = 1;
  long	       MemorySize = 0l;

  SystemLog.Printf ("Outputting custom allocation blocks...");
  
  for (pBlockInfo = pBIHead; pBlockInfo != NULL; pBlockInfo = pBlockInfo->pNext, Index++) {
    MemorySize += pBlockInfo->Size;
    SystemLog.Printf ("\t\t%3ld) %p (%s, %8u bytes), %s.", Index,
				pBlockInfo->pPointer, 
				pBlockInfo->pName == NULL ? "NULL" : pBlockInfo->pName, 
				pBlockInfo->Size, pBlockInfo->Referenced ? "is Referenced" : "NOT Referenced");
   }

  SystemLog.Printf ("\tOutput %ld bytes in %ld blocks...", MemorySize, Index - 1);
 }
/*===========================================================================
 *		End of Function OutputBlockInfo()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - size_t SizeOfBlock (pBlock);
 *
 * Attempts to get the allocated size of the given block.  The function
 * will ASSERT if the passed pointer is invalid.
 *
 *=========================================================================*/
size_t SizeOfBlock (void* pBlock) {
  DEFINE_FUNCTION("SizeOfBlock()");
  blockinfo_t* pBlockInfo;

  pBlockInfo = GetBlockInfo(pBlock);
  ASSERT((byte *) pBlock == pBlockInfo->pPointer);

  return (pBlockInfo->Size);
 }
/*===========================================================================
 *		End of Function SizeOfBlock()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - void UpdateBlockInfo (pOldBlock, pNewBlock, NewSize);
 *
 * Attempts to update a log structure for a memory block.  If any of the 
 * input parameters are invalid, the function will ASSERT.
 *
 *=========================================================================*/
void UpdateBlockInfo (void* pOldBlock, void* pNewBlock, const size_t NewSize) {
  DEFINE_FUNCTION("UpdateBlockInfo()");
  blockinfo_t* pBlockInfo;

	/* Ensure valid input */
  ASSERT(pNewBlock != NULL && NewSize != 0);

	/* Attempt to retrieve the block information */
  pBlockInfo = GetBlockInfo(pOldBlock);
  ASSERT((byte *) pOldBlock == pBlockInfo->pPointer);

	/* Update the log information */
  pBlockInfo->pPointer = (byte *) pNewBlock;
  pBlockInfo->Size = NewSize;
 }
/*===========================================================================
 *		End of Function UpdateBlockInfo()
 *=========================================================================*/


#endif	/* End of if defined(_DEBUG) */