/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    debug.c

Abstract:

    Debug helpers and memory allocation wrappers

Author:

    Jim Schmidt (jimschm) 13-Aug-1996

Revision History:

    Marc R. Whitten (marcw) 27-May-1997
        Added DEBUGLOGTIME() functions and support for the /#U:DOLOG cmd line option.
    Ovidiu Temereanca (ovidiut) 06-Nov-1998
        Took out log related functions and put them in log.c file
--*/

#include "pch.h"
#include "migutilp.h"

//
// NOTE: No code should appear outside the #ifdef DEBUG
//

#ifdef DEBUG

#pragma message("DEBUG macros enabled")

#define PCVOID LPCVOID

typedef DWORD ALLOCATION_ITEM_OFFSET;

typedef struct _tagTRACKBUCKETITEM {
    struct _tagTRACKBUCKETITEM *Next;
    struct _tagTRACKBUCKETITEM *Prev;
    ALLOCTYPE Type;
    PVOID Ptr;
    ALLOCATION_ITEM_OFFSET ItemOffset;
} TRACKBUCKETITEM, *PTRACKBUCKETITEM;

#define TRACK_BUCKETS   1501

PTRACKBUCKETITEM g_TrackBuckets[TRACK_BUCKETS];

#define BUCKET_ITEMS_PER_POOL   8192

typedef struct _tagBUCKETPOOL {
    UINT Count;
    TRACKBUCKETITEM Items[BUCKET_ITEMS_PER_POOL];
} TRACKBUCKETPOOL, *PTRACKBUCKETPOOL;

PTRACKBUCKETITEM g_TrackPoolDelHead;
PTRACKBUCKETPOOL g_TrackPool;

typedef struct _tagTRACKSTRUCT {
    DWORD Signature;
    PCSTR File;
    DWORD Line;
    DWORD Size;
    PSTR Comment;
    struct _tagTRACKSTRUCT *PrevAlloc;
    struct _tagTRACKSTRUCT *NextAlloc;
} TRACKSTRUCT, *PTRACKSTRUCT;

PTRACKSTRUCT TrackHead = NULL;
#define TRACK_SIGNATURE     0x30405060

DWORD
pDebugHeapValidatePtrUnlocked (
    HANDLE hHeap,
    PCVOID CallerPtr,
    PCSTR File,
    DWORD  Line
    );





//
// The following pointer can be used to help identify memory leak sources.
// It is copied to the memory tracking log.
//

PCSTR g_TrackComment;
PCSTR g_TrackFile;
UINT g_TrackLine;
INT g_UseCount;
UINT g_DisableTrackComment = 0;

VOID
DisableTrackComment (
    VOID
    )
{
    g_DisableTrackComment ++;
}

VOID
EnableTrackComment (
    VOID
    )
{
    if (g_DisableTrackComment > 0) {
        g_DisableTrackComment --;
    }
}

DWORD
SetTrackComment (
    PCSTR Msg,
    PCSTR File,
    UINT Line
    )
{
    static CHAR Buffer[1024];
    static CHAR FileCopy[1024];

    if (g_DisableTrackComment > 0) {
        return 0;
    }

    if (g_UseCount > 0) {
        g_UseCount++;
        return 0;
    }

    if (Msg) {
        wsprintfA (Buffer, "%s (%s line %u)", Msg, File, Line);
    } else {
        wsprintfA (Buffer, "%s line %u", File, Line);
    }

    StringCopyA (FileCopy, File);
    g_TrackFile = FileCopy;
    g_TrackLine = Line;

    g_TrackComment = Buffer;
    g_UseCount = 1;

    return 0;
}

DWORD
ClrTrackComment (
    VOID
    )
{
    if (g_DisableTrackComment > 0) {
        return 0;
    }

    g_UseCount--;

    if (!g_UseCount) {
        g_TrackComment=NULL;
    }

    return 0;
}


VOID
pTrackInsert (
    PCSTR File,
    DWORD Line,
    DWORD Size,
    PTRACKSTRUCT p
    )
{
    p->Signature = TRACK_SIGNATURE;
    p->File      = File;
    p->Line      = Line;
    p->Size      = Size;
    p->Comment   = g_TrackComment ? SafeHeapAlloc (g_hHeap, 0, SizeOfStringA (g_TrackComment)) : NULL;
    p->PrevAlloc = NULL;
    p->NextAlloc = TrackHead;

    if (p->Comment) {
        StringCopyA (p->Comment, g_TrackComment);
    }

    if (TrackHead) {
        TrackHead->PrevAlloc = p;
    }

    TrackHead = p;
}

VOID
pTrackDelete (
    PTRACKSTRUCT p
    )
{
    if (p->Signature != TRACK_SIGNATURE) {
        DEBUGMSG ((DBG_WARNING, "A tracking signature is invalid.  "
                                "This suggests memory corruption."));
        return;
    }

    if (p->PrevAlloc) {
        p->PrevAlloc->NextAlloc = p->NextAlloc;
    } else {
        TrackHead = p->NextAlloc;
    }

    if (p->NextAlloc) {
        p->NextAlloc->PrevAlloc = p->PrevAlloc;
    }
}

VOID
pWriteTrackLog (
    VOID
    )
{
    HANDLE File;
    CHAR LineBuf[2048];
    PTRACKSTRUCT p;
    DWORD DontCare;
    DWORD Count;
    BOOL BadMem = FALSE;
    CHAR TempPath[MAX_TCHAR_PATH];
    CHAR memtrackLogPath[] = "c:\\memtrack.log";

    if (!TrackHead) {
        return;
    }

    if (ISPC98()) {
        GetSystemDirectory(TempPath, MAX_TCHAR_PATH);
        memtrackLogPath[0] = TempPath[0];
    }
    File = CreateFileA (memtrackLogPath, GENERIC_WRITE, 0, NULL,
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
                        );

    if (File != INVALID_HANDLE_VALUE) {
        Count = 0;
        __try {
            for (p = TrackHead ; p ; p = p->NextAlloc) {
                Count++;
                __try {
                    if (p->Comment) {
                        wsprintfA (LineBuf, "%s line %u\r\n  %s\r\n\r\n", p->File, p->Line, p->Comment);
                    } else {
                        wsprintfA (LineBuf, "%s line %u\r\n\r\n", p->File, p->Line);
                    }
                }
                __except (TRUE) {
                    wsprintfA (LineBuf, "Address %Xh was freed, but not by MemFree!!\r\n", p);
                    BadMem = TRUE;
                }
                WriteFile (File, LineBuf, ByteCountA (LineBuf), &DontCare, NULL);

                if (BadMem) {
                    break;
                }
            }
        }
        __except (TRUE) {
        }

        wsprintfA (LineBuf, "\r\n%i item%s allocated but not freed.\r\n", Count, Count == 1 ? "":"s");
        WriteFile (File, LineBuf, ByteCountA (LineBuf), &DontCare, NULL);

        CloseHandle (File);
    }
}

typedef struct {
    ALLOCTYPE Type;
    PVOID Ptr;
    PCSTR FileName;
    UINT Line;
} ALLOCATION_ITEM, *PALLOCATION_ITEM;

GROWBUFFER g_AllocationList;
PVOID g_FirstDeletedAlloc;

VOID
InitAllocationTracking (
    VOID
    )
{
    ZeroMemory (&g_AllocationList, sizeof (g_AllocationList));
    g_AllocationList.GrowSize = 65536;
    g_FirstDeletedAlloc = NULL;
}

VOID
FreeAllocationTracking (
    VOID
    )
{
    UINT Size;
    UINT u;
    PALLOCATION_ITEM Item;
    GROWBUFFER Msg = GROWBUF_INIT;
    CHAR Text[1024];
    PSTR p;
    UINT Bytes;

    Size = g_AllocationList.End / sizeof (ALLOCATION_ITEM);;

    for (u = 0 ; u < Size ; u++) {
        Item = (PALLOCATION_ITEM) g_AllocationList.Buf + u;
        if (!Item->FileName) {
            continue;
        }

        Bytes = wsprintfA (Text, "%s line %u\r\n", Item->FileName, Item->Line);

        p = (PSTR) RealGrowBuffer (&Msg, Bytes);
        if (p) {
            CopyMemory (p, Text, Bytes);
        }
    }

    if (Msg.End) {

        p = (PSTR) RealGrowBuffer (&Msg, 1);
        if (p) {
            *p = 0;
            DEBUGMSGA (("Leaks", "%s", Msg.Buf));
        }

        FreeGrowBuffer (&Msg);
    }

    FreeGrowBuffer (&g_AllocationList);
    g_FirstDeletedAlloc = NULL;

    // Intentional leak -- who cares about track memory
    g_TrackPoolDelHead = NULL;
    g_TrackPool = NULL;
}


PTRACKBUCKETITEM
pAllocTrackBucketItem (
    VOID
    )
{
    PTRACKBUCKETITEM BucketItem;

    if (g_TrackPoolDelHead) {
        BucketItem = g_TrackPoolDelHead;
        g_TrackPoolDelHead = BucketItem->Next;
    } else {

        if (!g_TrackPool || g_TrackPool->Count == BUCKET_ITEMS_PER_POOL) {
            g_TrackPool = (PTRACKBUCKETPOOL) SafeHeapAlloc (g_hHeap, 0, sizeof (TRACKBUCKETPOOL));
            if (!g_TrackPool) {
                return NULL;
            }

            g_TrackPool->Count = 0;
        }

        BucketItem = g_TrackPool->Items + g_TrackPool->Count;
        g_TrackPool->Count++;
    }

    return BucketItem;
}

VOID
pFreeTrackBucketItem (
    PTRACKBUCKETITEM BucketItem
    )
{
    BucketItem->Next = g_TrackPoolDelHead;
    g_TrackPoolDelHead = BucketItem;
}



DWORD
pComputeTrackHashVal (
    IN      ALLOCTYPE Type,
    IN      PVOID Ptr
    )
{
    DWORD Hash;

    Hash = (DWORD) (Type << 16) ^ (DWORD) Ptr;
    return Hash % TRACK_BUCKETS;
}


VOID
pTrackHashTableInsert (
    IN      PBYTE Base,
    IN      ALLOCATION_ITEM_OFFSET ItemOffset
    )
{
    DWORD Hash;
    PTRACKBUCKETITEM BucketItem;
    PALLOCATION_ITEM Item;

    Item = (PALLOCATION_ITEM) (Base + ItemOffset);

    Hash = pComputeTrackHashVal (Item->Type, Item->Ptr);

    BucketItem = pAllocTrackBucketItem();

    if (!BucketItem) {
        DEBUGMSG ((DBG_WHOOPS, "pTrackHashTableInsert failed to alloc memory"));
        return;
    }

    BucketItem->Prev = NULL;
    BucketItem->Next = g_TrackBuckets[Hash];
    BucketItem->Type = Item->Type;
    BucketItem->Ptr  = Item->Ptr;
    BucketItem->ItemOffset = ItemOffset;

    if (BucketItem->Next) {
        BucketItem->Next->Prev = BucketItem;
    }

    g_TrackBuckets[Hash] = BucketItem;
}

VOID
pTrackHashTableDelete (
    IN      PTRACKBUCKETITEM BucketItem
    )
{
    DWORD Hash;

    Hash = pComputeTrackHashVal (BucketItem->Type, BucketItem->Ptr);

    if (BucketItem->Prev) {
        BucketItem->Prev->Next = BucketItem->Next;
    } else {
        g_TrackBuckets[Hash] = BucketItem->Next;
    }

    if (BucketItem->Next) {
        BucketItem->Next->Prev = BucketItem->Prev;
    }

    pFreeTrackBucketItem (BucketItem);
}

PTRACKBUCKETITEM
pTrackHashTableFind (
    IN      ALLOCTYPE Type,
    IN      PVOID Ptr
    )
{
    PTRACKBUCKETITEM BucketItem;
    DWORD Hash;

    Hash = pComputeTrackHashVal (Type, Ptr);

    BucketItem = g_TrackBuckets[Hash];
    while (BucketItem) {
        if (BucketItem->Type == Type && BucketItem->Ptr == Ptr) {
            return BucketItem;
        }

        BucketItem = BucketItem->Next;
    }

    return NULL;
}


VOID
DebugRegisterAllocation (
    IN      ALLOCTYPE Type,
    IN      PVOID Ptr,
    IN      PCSTR File,
    IN      UINT Line
    )
{
    PALLOCATION_ITEM Item;

    MYASSERT (File);

    if (!g_FirstDeletedAlloc) {
        Item = (PALLOCATION_ITEM) RealGrowBuffer (&g_AllocationList,sizeof(ALLOCATION_ITEM));
    } else {
        Item = (PALLOCATION_ITEM) g_FirstDeletedAlloc;
        g_FirstDeletedAlloc = Item->Ptr;
    }

    if (Item) {
        Item->Type = Type;
        Item->Ptr = Ptr;
        Item->FileName = File;
        Item->Line = Line;

        pTrackHashTableInsert (g_AllocationList.Buf, (PBYTE) Item - g_AllocationList.Buf);
    }
}


VOID
DebugUnregisterAllocation (
    IN      ALLOCTYPE Type,
    IN      PVOID Ptr
    )
{
    PALLOCATION_ITEM Item;
    PTRACKBUCKETITEM BucketItem;

    BucketItem = pTrackHashTableFind (Type, Ptr);
    if (!g_AllocationList.Buf) {
        DEBUGMSG ((DBG_WARNING, "Unregister allocation: Allocation buffer already freed"));
        return;
    }

    if (BucketItem) {
        Item = (PALLOCATION_ITEM) (g_AllocationList.Buf + BucketItem->ItemOffset);

        Item->FileName = NULL;
        Item->Type = -1;
        Item->Ptr = g_FirstDeletedAlloc;
        g_FirstDeletedAlloc = Item;

        pTrackHashTableDelete (BucketItem);

    } else {
        DEBUGMSG ((DBG_WARNING, "Unregister allocation: Pointer not registered"));
    }
}



//
// File and Line settings
//

static PCSTR g_File;
static DWORD  g_Line;

void
HeapCallFailed (
    PCSTR Msg,
    PCSTR File,
    DWORD Line
    )
{
    CHAR Msg2[2048];

    wsprintfA (Msg2, "Error in %s line %u\n\n", File, Line);
    strcat (Msg2, Msg);
    strcat (Msg2, "\n\nBreak execution now?");

    if (IDYES == MessageBoxA (GetFocus(), Msg2, "Heap Call Failed", MB_YESNO|MB_APPLMODAL)) {
        DebugBreak ();
    }
}


#define INVALID_PTR     0xffffffff


DWORD
DebugHeapValidatePtr (
    HANDLE hHeap,
    PCVOID CallerPtr,
    PCSTR File,
    DWORD  Line
    )
{
    DWORD rc;

    EnterCriticalSection (&g_MemAllocCs);

    rc = pDebugHeapValidatePtrUnlocked (hHeap, CallerPtr, File, Line);

    LeaveCriticalSection (&g_MemAllocCs);

    return rc;
}


DWORD
pDebugHeapValidatePtrUnlocked (
    HANDLE hHeap,
    PCVOID CallerPtr,
    PCSTR File,
    DWORD  Line
    )
{
    DWORD dwSize;
    PCVOID RealPtr;
    DWORD SizeAdjust;

    SizeAdjust = sizeof (TRACKSTRUCT);
    RealPtr = (PCVOID) ((PBYTE) CallerPtr - SizeAdjust);

    if (IsBadWritePtr ((PBYTE) RealPtr - 8, 8)) {
        CHAR BadPtrMsg[256];

        wsprintfA (
            BadPtrMsg,
            "Attempt to free memory at 0x%08x.  This address is not valid.",
            CallerPtr
            );

        HeapCallFailed (BadPtrMsg, File, Line);

        return INVALID_PTR;
    }

    dwSize = HeapSize (hHeap, 0, RealPtr);
    if (dwSize == 0xffffffff) {
        CHAR BadPtrMsg[256];

        wsprintfA (
            BadPtrMsg,
            "Attempt to free memory at 0x%08x.  "
                "This address is not the start of a memory block.",
            CallerPtr
            );

        HeapCallFailed (BadPtrMsg, File, Line);

        return INVALID_PTR;
    }

    return dwSize;
}



//
// Heap debug statistics
//

static DWORD g_dwTotalBytesAllocated = 0;
static DWORD g_dwMaxBytesInUse = 0;
static DWORD g_dwHeapAllocs = 0;
static DWORD g_dwHeapReAllocs = 0;
static DWORD g_dwHeapFrees = 0;
static DWORD g_dwHeapAllocFails = 0;
static DWORD g_dwHeapReAllocFails = 0;
static DWORD g_dwHeapFreeFails = 0;
#define TRAIL_SIG       0x708aa210

PVOID
DebugHeapAlloc (
    PCSTR File,
    DWORD Line,
    HANDLE hHeap,
    DWORD Flags,
    DWORD BytesToAlloc
    )
{
    PVOID RealPtr;
    PVOID ReturnPtr = NULL;
    DWORD SizeAdjust;
    DWORD TrackStructSize;
    DWORD OrgError;

    EnterCriticalSection (&g_MemAllocCs);

    __try {

        OrgError = GetLastError();

        SizeAdjust = sizeof (TRACKSTRUCT) + sizeof (DWORD);
        TrackStructSize = sizeof (TRACKSTRUCT);

        if (!HeapValidate (hHeap, 0, NULL)) {
            HeapCallFailed ("Heap is corrupt!", File, Line);
            g_dwHeapAllocFails++;
            __leave;
        }

        RealPtr = SafeHeapAlloc(hHeap, Flags, BytesToAlloc + SizeAdjust);
        if (RealPtr) {
            g_dwHeapAllocs++;
            g_dwTotalBytesAllocated += HeapSize (hHeap, 0, RealPtr);
            g_dwMaxBytesInUse = max (g_dwMaxBytesInUse, g_dwTotalBytesAllocated);

            pTrackInsert (File, Line, BytesToAlloc, (PTRACKSTRUCT) RealPtr);
            *((PDWORD) ((PBYTE) RealPtr + TrackStructSize + BytesToAlloc)) = TRAIL_SIG;
        }
        else {
            g_dwHeapAllocFails++;
        }

        if (RealPtr) {
            ReturnPtr = (PVOID) ((PBYTE) RealPtr + TrackStructSize);
        }

        if (ReturnPtr && !(Flags & HEAP_ZERO_MEMORY)) {
            FillMemory (ReturnPtr, BytesToAlloc, 0xAA);
        }

        if (RealPtr) {
            SetLastError(OrgError);
        }
    }
    __finally {
        LeaveCriticalSection (&g_MemAllocCs);
    }

    return ReturnPtr;
}

PVOID
DebugHeapReAlloc (
    PCSTR File,
    DWORD Line,
    HANDLE hHeap,
    DWORD Flags,
    PCVOID CallerPtr,
    DWORD BytesToAlloc
    )
{
    DWORD dwLastSize;
    PVOID NewRealPtr;
    PCVOID RealPtr;
    PVOID ReturnPtr = NULL;
    DWORD SizeAdjust;
    DWORD OrgError;
    DWORD TrackStructSize;
    DWORD OrgSize;
    PTRACKSTRUCT pts = NULL;

    EnterCriticalSection (&g_MemAllocCs);

    __try {

        OrgError = GetLastError();

        SizeAdjust = sizeof (TRACKSTRUCT) + sizeof (DWORD);
        TrackStructSize = sizeof (TRACKSTRUCT);
        RealPtr = (PCVOID) ((PBYTE) CallerPtr - TrackStructSize);
        pts = (PTRACKSTRUCT) RealPtr;
        OrgSize = pts->Size;

        if (!HeapValidate (hHeap, 0, NULL)) {
            HeapCallFailed ("Heap is corrupt!", File, Line);
            g_dwHeapReAllocFails++;
            __leave;
        }

        dwLastSize = pDebugHeapValidatePtrUnlocked (hHeap, CallerPtr, File, Line);
        if (dwLastSize == INVALID_PTR) {
            g_dwHeapReAllocFails++;
            __leave;
        }

        pTrackDelete (pts);

        NewRealPtr = SafeHeapReAlloc (hHeap, Flags, (PVOID) RealPtr, BytesToAlloc + SizeAdjust);
        if (NewRealPtr) {
            g_dwHeapReAllocs++;
            g_dwTotalBytesAllocated -= dwLastSize;
            g_dwTotalBytesAllocated += HeapSize (hHeap, 0, NewRealPtr);
            g_dwMaxBytesInUse = max (g_dwMaxBytesInUse, g_dwTotalBytesAllocated);

            pTrackInsert (File, Line, BytesToAlloc, (PTRACKSTRUCT) NewRealPtr);
            *((PDWORD) ((PBYTE) NewRealPtr + TrackStructSize + BytesToAlloc)) = TRAIL_SIG;
        }
        else {
            g_dwHeapReAllocFails++;

            // Put original address back in
            pTrackInsert (
                pts->File,
                pts->Line,
                pts->Size,
                pts
                );

        }

        if (NewRealPtr) {
            ReturnPtr = (PVOID) ((PBYTE) NewRealPtr + TrackStructSize);
        }

        if (ReturnPtr && BytesToAlloc > OrgSize && !(Flags & HEAP_ZERO_MEMORY)) {
            FillMemory ((PBYTE) ReturnPtr + OrgSize, BytesToAlloc - OrgSize, 0xAA);
        }

        if (ReturnPtr) {
            SetLastError (OrgError);
        }
    }
    __finally {
        LeaveCriticalSection (&g_MemAllocCs);
    }

    return ReturnPtr;
}

BOOL
DebugHeapFree (
    PCSTR File,
    DWORD Line,
    HANDLE hHeap,
    DWORD Flags,
    PCVOID CallerPtr
    )
{
    DWORD dwSize;
    PCVOID RealPtr;
    DWORD SizeAdjust;
    DWORD OrgError;
    BOOL Result = FALSE;
    PTRACKSTRUCT pts = NULL;

    EnterCriticalSection (&g_MemAllocCs);

    __try {
        OrgError = GetLastError();

        SizeAdjust = sizeof (TRACKSTRUCT);
        RealPtr = (PCVOID) ((PBYTE) CallerPtr - SizeAdjust);
        pts = (PTRACKSTRUCT) RealPtr;

        if (*((PDWORD) ((PBYTE) CallerPtr + pts->Size)) != TRAIL_SIG) {
            HeapCallFailed ("Heap tag was overwritten!", File, Line);
            __leave;
        }

        if (!HeapValidate (hHeap, 0, NULL)) {
            HeapCallFailed ("Heap is corrupt!", File, Line);
            g_dwHeapFreeFails++;
            __leave;
        }

        dwSize = pDebugHeapValidatePtrUnlocked (hHeap, CallerPtr, File, Line);
        if (dwSize == INVALID_PTR) {
            g_dwHeapFreeFails++;
            __leave;
        }

        pTrackDelete ((PTRACKSTRUCT) RealPtr);

        if (!HeapFree (hHeap, Flags, (PVOID) RealPtr)) {
            CHAR BadPtrMsg[256];

            wsprintf (BadPtrMsg,
                      "Attempt to free memory at 0x%08x with flags 0x%08x.  "
                      "HeapFree() failed.",
                      CallerPtr, Flags);

            HeapCallFailed (BadPtrMsg, File, Line);
            g_dwHeapFreeFails++;
            __leave;
        }

        g_dwHeapFrees++;
        if (g_dwTotalBytesAllocated < dwSize) {
            DEBUGMSG ((DBG_WARNING, "Total bytes allocated is less than amount being freed.  "
                                    "This suggests memory corruption."));
            g_dwTotalBytesAllocated = 0;
        } else {
            g_dwTotalBytesAllocated -= dwSize;
        }

        SetLastError (OrgError);
        Result = TRUE;
    }
    __finally {
        LeaveCriticalSection (&g_MemAllocCs);
    }

    return Result;

}


VOID
DumpHeapStats (
    VOID
    )
{
    CHAR OutputMsg[4096];

    pWriteTrackLog();

    wsprintfA (OutputMsg,
               "Bytes currently allocated: %u\n"
               "Peak bytes allocated: %u\n"
               "Allocation count: %u\n"
               "Reallocation count: %u\n"
               "Free count: %u\n",
               g_dwTotalBytesAllocated,
               g_dwMaxBytesInUse,
               g_dwHeapAllocs,
               g_dwHeapReAllocs,
               g_dwHeapFrees
               );

    if (g_dwHeapAllocFails) {
        wsprintfA (strchr (OutputMsg, 0),
                   "***Allocation failures: %u\n",
                   g_dwHeapAllocFails);
    }
    if (g_dwHeapReAllocFails) {
        wsprintfA (strchr (OutputMsg, 0),
                   "***Reallocation failures: %u\n",
                   g_dwHeapReAllocFails);
    }
    if (g_dwHeapFreeFails) {
        wsprintfA (strchr (OutputMsg, 0),
                   "***Free failures: %u\n",
                   g_dwHeapFreeFails);
    }

    DEBUGMSG ((DBG_STATS, "%s", OutputMsg));

#ifdef CONSOLE
    printf ("%s", OutputMsg);
#else  // i.e. ifndef CONSOLE

#if 0
    if (0) {
        PROCESS_HEAP_ENTRY he;
        CHAR FlagMsg[256];

        ZeroMemory (&he, sizeof (he));

        while (HeapWalk (g_hHeap, &he)) {
            FlagMsg[0] = 0;
            if (he.wFlags & PROCESS_HEAP_REGION) {
                strcpy (FlagMsg, "PROCESS_HEAP_REGION");
            }
            if (he.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE) {
                if (FlagMsg[0])
                    strcat (FlagMsg, ", ");

                strcat (FlagMsg, "PROCESS_HEAP_UNCOMMITTED_RANGE");
            }
            if (he.wFlags & PROCESS_HEAP_ENTRY_BUSY) {
                if (FlagMsg[0])
                    strcat (FlagMsg, ", ");

                strcat (FlagMsg, "PROCESS_HEAP_ENTRY_BUSY");
            }
            if (he.wFlags & PROCESS_HEAP_ENTRY_MOVEABLE) {
                if (FlagMsg[0])
                    strcat (FlagMsg, ", ");

                strcat (FlagMsg, "PROCESS_HEAP_ENTRY_MOVEABLE");
            }
            if (he.wFlags & PROCESS_HEAP_ENTRY_DDESHARE) {
                if (FlagMsg[0])
                    strcat (FlagMsg, ", ");

                strcat (FlagMsg, "PROCESS_HEAP_ENTRY_DDESHARE");
            }

            wsprintfA (OutputMsg,
                       "Address of Data: %Xh\n"
                       "Size of Data: %u byte%s\n"
                       "OS Overhead: %u byte%s\n"
                       "Region index: %u\n"
                       "Flags: %s\n\n"
                       "Examine Data?",
                       he.lpData,
                       he.cbData, he.cbData == 1 ? "" : "s",
                       he.cbOverhead, he.cbOverhead == 1 ? "" : "s",
                       he.iRegionIndex,
                       FlagMsg
                       );

            rc = MessageBoxA (GetFocus(), OutputMsg, "Memory Allocation Statistics", MB_YESNOCANCEL|MB_APPLMODAL|MB_SETFOREGROUND);

            if (rc == IDCANCEL) {
                break;
            }

            if (rc == IDYES) {
                int i, j, k, l;
                PBYTE p;
                PSTR p2;
                OutputMsg[0] = 0;

                p = he.lpData;
                p2 = OutputMsg;
                j = min (256, he.cbData);
                for (i = 0 ; i < j ; i += 16) {
                    l = i + 16;
                    for (k = i ; k < l ; k++) {
                        if (k < j) {
                            wsprintfA (p2, "%02X ", (DWORD) (p[k]));
                        } else {
                            wsprintfA (p2, "   ");
                        }

                        p2 = strchr (p2, 0);
                    }

                    l = min (l, j);
                    for (k = i ; k < l ; k++) {
                        if (isprint (p[k])) {
                            *p2 = (CHAR) p[k];
                        } else {
                            *p2 = '.';
                        }
                        p2++;
                    }

                    *p2 = '\n';
                    p2++;
                    *p2 = 0;
                }

                MessageBoxA (GetFocus(), OutputMsg, "Memory Allocation Statistics", MB_OK|MB_APPLMODAL|MB_SETFOREGROUND);
            }
        }
    }
#endif // #if 0

#endif // #ifndef CONSOLE
}

void
DebugHeapCheck (
    PCSTR File,
    DWORD Line,
    HANDLE hHeap
    )
{
    EnterCriticalSection (&g_MemAllocCs);

    if (!HeapValidate (hHeap, 0, NULL)) {
        HeapCallFailed ("HeapCheck failed: Heap is corrupt!", File, Line);
    }

    LeaveCriticalSection (&g_MemAllocCs);
}

#endif