//+------------------------------------------------------------------------
//
//  File:       memutil.cxx
//
//  Contents:   Memory utilities
//
//  History:    Stolen from Trident
//
//-------------------------------------------------------------------------

#include "headers.hxx"

EXTERN_C HANDLE g_hProcessHeap;

#define SMALLBLOCKHEAP 0

void
ClearInterfaceFn(IUnknown **ppUnk)
{
    IUnknown * pUnk;

    pUnk = *ppUnk;
    *ppUnk = NULL;
    if (pUnk)
        pUnk->Release();
}


//+------------------------------------------------------------------------
// Allocation functions not implemented in this file:
//
//      CDUTIL.HXX
//          operator new
//          operator delete
//
//      OLE's OBJBASE.H
//          CoTaskMemAlloc, CoTaskMemFree
//
//-------------------------------------------------------------------------

#if SMALLBLOCKHEAP

DeclareTag(tagSmallBlockHeap, "!Memory", "Check small block heap every time")
DeclareTag(tagSmallBlockHeapDisable, "!Memory", "Disable small block heap");

#define _CRTBLD 1
#include "winheap.h"
EXTERN_C CRITICAL_SECTION g_csHeap;

#if DBG == 1
#define CHECKSBH if (IsTagEnabled(tagSmallBlockHeap)) {Assert(CheckSmallBlockHeap() && "Small block heap corrupt");};
BOOL IsSmallBlockHeapEnabled()
{
    static int g_fSmallBlockHeap = -1;
    if (g_fSmallBlockHeap == -1)
        g_fSmallBlockHeap = IsTagEnabled(tagSmallBlockHeapDisable) ? 0 : 1;
    return(g_fSmallBlockHeap == 1);
}
BOOL CheckSmallBlockHeap()
{
    if (IsSmallBlockHeapEnabled())
    {
        EnterCriticalSection(&g_csHeap);
        BOOL f = __sbh_heap_check() >= 0;
        LeaveCriticalSection(&g_csHeap);
        return f;
    }
    return TRUE;
}
#else
#define CHECKSBH
#endif

#else

#if DBG == 1
BOOL CheckSmallBlockHeap()
{
    return TRUE;
}
#endif

#endif SMALLBLOCKHEAP

//+------------------------------------------------------------------------
//
//  Function:   _MemGetSize
//
//  Synopsis:   Get size of block allocated with MemAlloc/MemRealloc.
//
//              Note that MemAlloc/MemRealloc can allocate more than
//              the requested number of bytes. Therefore the size returned
//              from this function is possibly greater than the size
//              passed to MemAlloc/Realloc.
//
//  Arguments:  [pv] - Return size of this block.
//
//  Returns:    The size of the block, or zero of pv == NULL.
//
//-------------------------------------------------------------------------
ULONG
_MemGetSize(void *pv)
{
    if (pv == NULL)
        return 0;

    Assert(g_hProcessHeap);

#if SMALLBLOCKHEAP
#if DBG==1
    if (IsSmallBlockHeapEnabled())
#endif
    {
        __sbh_region_t *    preg;
        __sbh_page_t *      ppage;
        __map_t *           pmap;

        EnterCriticalSection(&g_csHeap);
        if ((pmap = __sbh_find_block(DbgPreGetSize(pv), &preg, &ppage)) != NULL )
        {
            size_t s = DbgPostGetSize(((size_t)(*pmap)) << _PARASHIFT);
            LeaveCriticalSection(&g_csHeap);
            return s;
        }
        LeaveCriticalSection(&g_csHeap);
    }
#endif

    return DbgPostGetSize(HeapSize(g_hProcessHeap, 0, DbgPreGetSize(pv)));
}

//+------------------------------------------------------------------------
//
//  Function:   _MemAlloc
//
//  Synopsis:   Allocate block of memory.
//
//              The contents of the block are undefined.  If the requested size
//              is zero, this function returns a valid pointer.  The returned
//              pointer is guaranteed to be suitably aligned for storage of any
//              object type.
//
//  Arguments:  [cb] - Number of bytes to allocate.
//
//  Returns:    Pointer to the allocated block, or NULL on error.
//
//-------------------------------------------------------------------------
void *
_MemAlloc(ULONG cb)
{
    AssertSz (cb, "Requesting zero sized block.");

    // The small-block heap will lose its mind if this ever happens, so we
    // protect against the possibility.

    if (cb == 0)
        cb = 1;

    Assert(g_hProcessHeap);

#if SMALLBLOCKHEAP
#if DBG==1
    if (IsSmallBlockHeapEnabled())
#endif
    {
        /* round up to the nearest paragraph */
        size_t cbr = (DbgPreAlloc(cb) + _PARASIZE - 1) & ~(_PARASIZE - 1);

        if (cbr < __sbh_threshold)
        {
            CHECKSBH;
            EnterCriticalSection(&g_csHeap);
            void * pv = DbgPostAlloc(__sbh_alloc_block(cbr >> _PARASHIFT));
            LeaveCriticalSection(&g_csHeap);
            if (pv)
                return pv;
        }
    }
#endif

    return DbgPostAlloc(HeapAlloc(g_hProcessHeap, 0, DbgPreAlloc(cb)));
}


//+------------------------------------------------------------------------
//  Function:   _MemAllocClear
//
//  Synopsis:   Allocate a zero filled block of memory.
//
//              If the requested size is zero, this function returns a valid
//              pointer. The returned pointer is guaranteed to be suitably
//              aligned for storage of any object type.
//
//  Arguments:  [cb] - Number of bytes to allocate.
//
//  Returns:    Pointer to the allocated block, or NULL on error.
//
//-------------------------------------------------------------------------
void *
_MemAllocClear(ULONG cb)
{
    AssertSz (cb, "Allocating zero sized block.");

    // The small-block heap will lose its mind if this ever happens, so we
    // protect against the possibility.

    if (cb == 0)
        cb = 1;

    void * pv;

    Assert(g_hProcessHeap);

#if SMALLBLOCKHEAP
#if DBG==1
    if (IsSmallBlockHeapEnabled())
#endif
    {
        /* round up to the nearest paragraph */
        size_t cbr = (DbgPreAlloc(cb) + _PARASIZE - 1) & ~(_PARASIZE - 1);

        if (cbr < __sbh_threshold)
        {
            CHECKSBH;
            EnterCriticalSection(&g_csHeap);
            pv = DbgPostAlloc(__sbh_alloc_block(cbr >> _PARASHIFT));
            LeaveCriticalSection(&g_csHeap);
            if (pv)
            {
                memset(pv, 0, cb);
                return pv;
            }
        }
    }
#endif

    pv = DbgPostAlloc(HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY,
                    DbgPreAlloc(cb)));

    // In debug, DbgPostAlloc set the memory so we need to clear it again.

#if DBG==1
    if (pv)
    {
        memset(pv, 0, cb);
    }
#endif

    return pv;
}

//+------------------------------------------------------------------------
//
//  Function:   _MemFree
//
//  Synopsis:   Free a block of memory allocated with MemAlloc,
//              MemAllocFree or MemRealloc.
//
//  Arguments:  [pv] - Pointer to block to free.  A value of zero is
//              is ignored.
//
//-------------------------------------------------------------------------

void
_MemFree(void *pv)
{
    // The null check is required for HeapFree.
    if (pv == NULL)
        return;

    Assert(g_hProcessHeap);

#if DBG == 1
    pv = DbgPreFree(pv);
#endif

#if SMALLBLOCKHEAP
#if DBG==1
    if (IsSmallBlockHeapEnabled())
#endif
    {
        __sbh_region_t *preg;
        __sbh_page_t *  ppage;
        __map_t *       pmap;

        CHECKSBH;
        EnterCriticalSection(&g_csHeap);
        if ( (pmap = __sbh_find_block(pv, &preg, &ppage)) != NULL ) {
            __sbh_free_block(preg, ppage, pmap);
            LeaveCriticalSection(&g_csHeap);
            DbgPostFree();
            return;
        }
        LeaveCriticalSection(&g_csHeap);
    }
#endif

    HeapFree(g_hProcessHeap, 0, pv);
    DbgPostFree();
}

//+------------------------------------------------------------------------
//  Function:   _MemRealloc
//
//  Synopsis:   Change the size of an existing block of memory, allocate a
//              block of memory, or free a block of memory depending on the
//              arguments.
//
//              If cb is zero, this function always frees the block of memory
//              and *ppv is set to zero.
//
//              If cb is not zero and *ppv is zero, then this function allocates
//              cb bytes.
//
//              If cb is not zero and *ppv is non-zero, then this function
//              changes the size of the block, possibly by moving it.
//
//              On error, *ppv is left unchanged.  The block contents remains
//              unchanged up to the smaller of the new and old sizes.  The
//              contents of the block beyond the old size is undefined.
//              The returned pointer is guaranteed to be suitably aligned for
//              storage of any object type.
//
//              The signature of this function is different than thy typical
//              realloc-like function to avoid the following common error:
//                  pv = realloc(pv, cb);
//              If realloc fails, then null is returned and the pointer to the
//              original block of memory is leaked.
//
//  Arguments:  [cb] - Requested size in bytes. A value of zero always frees
//                  the block.
//              [ppv] - On input, pointer to existing block pointer or null.
//                  On output, pointer to new block pointer.
//
//  Returns:    HRESULT
//
//-------------------------------------------------------------------------

HRESULT
_MemRealloc(void **ppv, ULONG cb)
{
    void *pv;

    Assert(g_hProcessHeap);

    if (cb == 0)
    {
        _MemFree(*ppv);
        *ppv = 0;
    }
    else if (*ppv == NULL)
    {
        *ppv = _MemAlloc(cb);
        if (*ppv == NULL)
            return E_OUTOFMEMORY;
    }
    else
    {
    #if DBG == 1
        cb = DbgPreRealloc(*ppv, cb, &pv);
    #else
        pv = *ppv;
    #endif

    #if SMALLBLOCKHEAP
    #if DBG==1
        if (IsSmallBlockHeapEnabled())
    #endif
        {
            __sbh_region_t *preg;
            __sbh_page_t *  ppage;
            __map_t *       pmap;
            ULONG           cbr;
            void *          pvNew;

            cbr = (cb + _PARASIZE - 1) & ~(_PARASIZE - 1);

            CHECKSBH;
            EnterCriticalSection(&g_csHeap);
            if ( (pmap = __sbh_find_block(pv, &preg, &ppage)) != NULL )
            {
                pvNew = NULL;
                /*
                 * If the new size falls below __sbh_threshold, try to
                 * carry out the reallocation within the small-block
                 * heap.
                 */
                if ( cbr < __sbh_threshold ) {
                    if ( __sbh_resize_block(preg, ppage, pmap, cbr >> _PARASHIFT))
                    {
                        pvNew = pv;
                    }
                    else if ((pvNew = __sbh_alloc_block(cbr >> _PARASHIFT)) != NULL)
                    {
                        ULONG cbOld = ((size_t)(*pmap)) << _PARASHIFT;
                        memcpy(pvNew, pv, min(cbOld, cb));
                        __sbh_free_block(preg, ppage, pmap);
                    }
                }

                /*
                 * If the reallocation has not been (successfully)
                 * performed in the small-block heap, try to allocate a
                 * new block with HeapAlloc.
                 */
                if ((pvNew == NULL) && ((pvNew = HeapAlloc(g_hProcessHeap, 0, cb)) != NULL))
                {
                    ULONG cbOld = ((size_t)(*pmap)) << _PARASHIFT;
                    memcpy(pvNew, pv, min(cbOld, cb));
                    __sbh_free_block(preg, ppage, pmap);
                }
                LeaveCriticalSection(&g_csHeap);
                *ppv = DbgPostRealloc(pvNew);
                if (*ppv)
                {
                    return S_OK;
                }
                else
                {
                    return E_OUTOFMEMORY;
                }
            }
            else
            {
                LeaveCriticalSection(&g_csHeap);
            }
        }
    #endif

        pv = DbgPostRealloc(HeapReAlloc(g_hProcessHeap, 0, pv, cb));

        if (pv == NULL)
            return E_OUTOFMEMORY;
        *ppv = pv;
    }

    return S_OK;
}

// MEMGUARD -------------------------------------------------------------------

#if defined(MEMGUARD)

#define MGGUARDDATA 0xF0F0BAAD

struct MGGUARD
{
    MGGUARD *pNext;
    DWORD    dw;
};

MGGUARD * g_pMemList = NULL;

void
_MgMemValidate()
{
    EnterCriticalSection(&g_csHeap);

    MGGUARD *pg = g_pMemList;

    while (pg)
    {
        if (pg->dw != MGGUARDDATA)
        {
            DebugBreak();
        }

        pg = pg->pNext;
    }

    LeaveCriticalSection(&g_csHeap);
}

void
_MgRemove(MGGUARD *pmg)
{
    if (!pmg)
        return;

    EnterCriticalSection(&g_csHeap);

    MGGUARD *pg = g_pMemList;

    if (pmg == pg)
    {
        g_pMemList = pg->pNext;
        goto Cleanup;
    }

    while (pg)
    {
        if (pg->pNext == pmg)
        {
            pg->pNext = pg->pNext->pNext;
            break;
        }

        pg = pg->pNext;
    }

Cleanup:
    LeaveCriticalSection(&g_csHeap);

}

void
_MgAdd(MGGUARD *pmg)
{
    EnterCriticalSection(&g_csHeap);

    pmg->pNext = g_pMemList;
    g_pMemList = pmg;

    LeaveCriticalSection(&g_csHeap);
}

void *
_MgMemAlloc(ULONG cb)
{
    _MgMemValidate();

    MGGUARD * pmg = (MGGUARD *)_MemAlloc(sizeof(MGGUARD) + cb);

    if (pmg)
    {
        pmg->dw = MGGUARDDATA;

        _MgAdd(pmg);

        return(pmg + 1);
    }
    else
    {
        return(NULL);
    }

}

void *
_MgMemAllocClear(ULONG cb)
{
    _MgMemValidate();

    MGGUARD * pmg = (MGGUARD *)_MemAllocClear(sizeof(MGGUARD) + cb);

    if (pmg)
    {
        pmg->dw = MGGUARDDATA;

        _MgAdd(pmg);

        return(pmg + 1);
    }
    else
    {
        return(NULL);
    }
}

HRESULT
_MgMemRealloc(void ** ppv, ULONG cb)
{
    _MgMemValidate();

    if (cb == 0)
    {
        _MgMemFree(*ppv);
        *ppv = 0;
        return(S_OK);
    }

    if (*ppv == NULL)
    {
        *ppv = _MgMemAlloc(cb);
        return(*ppv ? S_OK : E_OUTOFMEMORY);
    }

    MGGUARD *  pmg = (MGGUARD *)*ppv - 1;

    _MgRemove(pmg);

    HRESULT    hr = _MemRealloc((void **)&pmg, sizeof(MGGUARD) + cb);

    if (hr == S_OK)
    {
        pmg->dw = MGGUARDDATA;

        _MgAdd(pmg);

        *ppv = pmg + 1;
    }

    return(hr);
}

ULONG
_MgMemGetSize(void * pv)
{
    _MgMemValidate();

    if (pv == NULL)
        return(0);
    else
        return(_MemGetSize((MGGUARD *)pv - 1) - sizeof(MGGUARD));
}

void
_MgMemFree(void * pv)
{
    _MgMemValidate();

    if (pv)
    {
        MGGUARD * pmg = (MGGUARD *)pv - 1;
        if (pmg->dw != MGGUARDDATA)
        {
            // The memory guard DWORD was overwritten! Bogus!
#ifdef _M_IX86
            _asm int 3  // To get a proper stacktrace.
#else
            DebugBreak();
#endif
        }
        _MgRemove(pmg);

        _MemFree(pmg);
    }
}

HRESULT
_MgMemAllocString(LPCTSTR pchSrc, LPTSTR * ppchDst)
{
    TCHAR *pch;
    size_t cb;

    cb = (_tcsclen(pchSrc) + 1) * sizeof(TCHAR);
    *ppchDst = pch = (TCHAR *)_MgMemAlloc(cb);
    if (!pch)
        return E_OUTOFMEMORY;
    else
    {
        memcpy(pch, pchSrc, cb);
        return S_OK;
    }
}

HRESULT
_MgMemAllocString(ULONG cch, const TCHAR *pchSrc, TCHAR **ppchDest)
{
    TCHAR *pch;
    size_t cb = cch * sizeof(TCHAR);

    *ppchDest = pch = (TCHAR *)_MgMemAlloc(cb + sizeof(TCHAR));
    if (!pch)
        return E_OUTOFMEMORY;
    else
    {
        memcpy(pch, pchSrc, cb);
        pch[cch] = 0;
        return S_OK;
    }
}

HRESULT
_MgMemReplaceString(const TCHAR *pchSrc, TCHAR **ppchDest)
{
    HRESULT hr;
    TCHAR *pch;

    if (pchSrc)
    {
        hr = THR(_MgMemAllocString(pchSrc, &pch));
        if (hr)
            RRETURN(hr);
    }
    else
    {
        pch = NULL;
    }

    _MgMemFreeString(*ppchDest);
    *ppchDest = pch;

    return S_OK;
}

#endif // MEMGUARD
