/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** Author: ShonK Project: Kauai Reviewed: Copyright (c) Microsoft Corporation Base classes. Base class that all other classes are derived from and a base linked list class for implementing singly linked lists. ***************************************************************************/ #include "util.h" ASSERTNAME #ifdef DEBUG long vcactSuspendAssertValid = 0; long vcactAVSave = 0; long vcactAV = kswMax; /***************************************************************************** Debugging stuff to track leaked allocated objects. ******************************************************************************/ // Debugging object info. const long kclwStackDoi = 10; struct DOI { short swMagic; // magic number == kswMagicMem short cactRef; // for marking memory and asserting on unused objects PSZS pszsFile; // file NewObj appears in long lwLine; // line NewObj appears on long cbTot; // total size of the block, including the DOI long lwThread; // thread that allocated this long rglwStack[10]; // what we get from following the EBP/A6 chain DOI *pdoiNext; // singly linked list DOI **ppdoiPrev; }; #define kcbBaseDebug size(DOI) priv void _AssertDoi(DOI *pdoi, bool tLinked); inline DOI *_PdoiFromBase(void *pv) { return (DOI *)PvSubBv(pv, kcbBaseDebug); } inline BASE *_PbaseFromDoi(DOI *pdoi) { return (BASE *)PvAddBv(pdoi, kcbBaseDebug); } priv void _LinkDoi(DOI *pdoi, DOI **ppdoiFirst); priv void _UnlinkDoi(DOI *pdoi); DOI *_pdoiFirst; // Head of linked list of all allocated objects. DOI *_pdoiFirstRaw; // Head of linked list of raw newly allocated objects. inline void _Enter(void) { vmutxBase.Enter(); } inline void _Leave(void) { vmutxBase.Leave(); } #define klwMagicAllocatedBase 'ALOC' #define klwMagicNonAllocBase 'NOAL' #else //!DEBUG #define kcbBaseDebug 0 #endif //!DEBUG RTCLASS(BLL) /*************************************************************************** Returns the run-time class id (cls) of the class. ***************************************************************************/ long BASE::Cls(void) { return kclsBASE; } /*************************************************************************** Returns true iff cls is kclsBASE. ***************************************************************************/ bool BASE::FIs(long cls) { return kclsBASE == cls; } /*************************************************************************** Static method. Returns true iff cls is kclsBASE. ***************************************************************************/ bool BASE::FWouldBe(long cls) { return kclsBASE == cls; } /*************************************************************************** Constructor for a BASE object. Sets _cactRef to 1 and in debug sets the magic number to indicate whether the thing was allocated. ***************************************************************************/ BASE::BASE(void) { _cactRef = 1; #ifdef DEBUG DOI *pdoi = _pdoiFirstRaw; if (pvNil != pdoi) { // Note that we have to check _pdoiFirstRaw before entering the // mutx so we don't try to enter the mutx before it's been // initialized (during global object construction). _Enter(); // see if this is in the raw list - note that we have to refresh // _pdoiFirstRaw in case another thread grabbed it before we got // the critical section for (pdoi = _pdoiFirstRaw; pdoi != pvNil && this != _PbaseFromDoi(pdoi); pdoi = pdoi->pdoiNext) { _AssertDoi(pdoi, tYes); } if (pvNil != pdoi) { // this is us! _UnlinkDoi(pdoi); _LinkDoi(pdoi, &_pdoiFirst); _lwMagic = klwMagicAllocatedBase; AssertValid(0); } _Leave(); } if (pvNil == pdoi) { _lwMagic = klwMagicNonAllocBase; //don't call AssertValid here, since this may be during //global initialization (FAssertProc and/or AssertPvCb may not be //callable). } #endif //DEBUG } /*************************************************************************** Increments the reference count. ***************************************************************************/ void BASE::AddRef(void) { AssertThis(0); _cactRef++; //NOTE: some classes allow _cactRef == 0, so we can't assert _cactRef > 1 Assert(_cactRef > 0, "_cactRef not positive"); } /*************************************************************************** Decrement the reference count and delete it if the reference count goes to zero. ***************************************************************************/ void BASE::Release(void) { AssertThis(0); if (--_cactRef <= 0) { AssertThis(fobjAllocated); delete this; } } /*************************************************************************** Used to allocate all objects. Clears the block and in debug, adds magic number, reference count for marking and inserts in linked list of allocated objects for object leakage tracking. ***************************************************************************/ #ifdef DEBUG void *BASE::operator new(size_t cb, PSZS pszsFile, long lwLine) #else //!DEBUG void *BASE::operator new(size_t cb) #endif //!DEBUG { AssertVarMem(pszsFile); void *pv; #ifdef DEBUG // do failure simulation _Enter(); if (vdmglob.dmaglBase.FFail()) { _Leave(); PushErc(ercOomNew); return pvNil; } _Leave(); #endif //DEBUG #ifdef WIN if ((pv = (void *)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, cb + kcbBaseDebug)) == pvNil) #else //!WIN if ((pv = ::operator new(cb + kcbBaseDebug)) == pvNil) #endif //!WIN PushErc(ercOomNew); else { #ifdef DEBUG _Enter(); DOI *pdoi = (DOI *)pv; pdoi->swMagic = kswMagicMem; pdoi->cactRef = 0; pdoi->pszsFile = pszsFile; pdoi->lwLine = lwLine; pdoi->cbTot = cb + kcbBaseDebug; pdoi->lwThread = LwThreadCur(); pdoi->ppdoiPrev = pvNil; _LinkDoi(pdoi, &_pdoiFirstRaw); pv = _PbaseFromDoi(pdoi); #ifdef WIN // follow the EBP chain.... long *plw; long ilw; __asm { mov plw,ebp } for (ilw = 0; ilw < kclwStackDoi; ilw++) { if (pvNil == plw || IsBadReadPtr(plw, 2 * size(long)) || *plw <= (long)plw) { pdoi->rglwStack[ilw] = 0; plw = pvNil; } else { pdoi->rglwStack[ilw] = plw[1]; plw = (long *)*plw; } } #endif //WIN // update statistics vdmglob.dmaglBase.Allocate(cb); _Leave(); #endif //DEBUG #ifndef WIN ClearPb(pv, cb); #endif //!WIN } return pv; } #if defined(DEBUG) || defined(WIN) /*************************************************************************** DEBUG : Unlink from linked list of allocated objects and free the memory. ***************************************************************************/ void BASE::operator delete(void *pv) { #ifdef DEBUG DOI *pdoi = _PdoiFromBase(pv); _UnlinkDoi(pdoi); // update statistics vdmglob.dmaglBase.Free(pdoi->cbTot - kcbBaseDebug); TrashPvCb(pdoi, pdoi->cbTot); pv = pdoi; #endif // DEBUG #ifdef WIN GlobalFree((HGLOBAL)pv); #else //!WIN ::delete(pv); #endif //!WIN } #endif //DEBUG || WIN #ifdef DEBUG /*************************************************************************** Assert the this pointer is valid. ***************************************************************************/ void BASE::AssertValid(ulong grfobj) { AssertVarMem(this); AssertIn(_cactRef, 0, kcbMax); if (_lwMagic != klwMagicAllocatedBase) { Assert(!(grfobj & fobjAllocated), "should be allocated"); AssertVar(_lwMagic == klwMagicNonAllocBase, "_lwMagic wrong", &_lwMagic); AssertPvCb(this, size(BASE)); return; } Assert(!(grfobj & fobjNotAllocated), "should not be allocated"); _Enter(); DOI *pdoi = _PdoiFromBase(this); _AssertDoi(pdoi, tYes); _Leave(); } /*************************************************************************** If this objects has already been marked, just return. If not, call its MarkMem method. ***************************************************************************/ void BASE::MarkMemStub(void) { AssertThis(0); if (_lwMagic != klwMagicAllocatedBase) { AssertVar(_lwMagic == klwMagicNonAllocBase, "_lwMagic wrong", &_lwMagic); MarkMem(); return; } DOI *pdoi = _PdoiFromBase(this); if (pdoi->cactRef == 0 && pdoi->lwThread == LwThreadCur()) MarkMem(); } /*************************************************************************** Mark object. ***************************************************************************/ void BASE::MarkMem(void) { AssertValid(0); if (_lwMagic == klwMagicAllocatedBase) _PdoiFromBase(this)->cactRef++; } /*************************************************************************** Assert that a doi is valid and optionally linked or unlinked. ***************************************************************************/ void _AssertDoi(DOI *pdoi, bool tLinked) { _Enter(); AssertPvCb(pdoi, size(BASE) + kcbBaseDebug); AssertIn(pdoi->cbTot, size(BASE) + kcbBaseDebug, kcbMax); AssertPvCb(pdoi, pdoi->cbTot); AssertVar(pdoi->swMagic == kswMagicMem, "magic number has been hammered", &pdoi->swMagic); AssertVar(pdoi->cactRef >= 0, "negative reference count", &pdoi->cactRef); if (pvNil == pdoi->ppdoiPrev) Assert(tLinked != tYes, "should be linked"); else { Assert(tLinked != tNo, "should NOT be linked"); AssertVarMem(pdoi->ppdoiPrev); AssertVar(*pdoi->ppdoiPrev == pdoi, "*ppdoiPrev is wrong", pdoi->ppdoiPrev); if (pdoi->pdoiNext != pvNil) { AssertVarMem(pdoi->pdoiNext); AssertVar(pdoi->pdoiNext->ppdoiPrev == &pdoi->pdoiNext, "ppdoiPrev in next is wrong", &pdoi->pdoiNext->ppdoiPrev); } } _Leave(); } /*************************************************************************** Link object into list. ***************************************************************************/ priv void _LinkDoi(DOI *pdoi, DOI **ppdoiFirst) { _Enter(); _AssertDoi(pdoi, tNo); AssertVarMem(ppdoiFirst); if (*ppdoiFirst != pvNil) { _AssertDoi(*ppdoiFirst, tYes); (*ppdoiFirst)->ppdoiPrev = &pdoi->pdoiNext; } pdoi->pdoiNext = *ppdoiFirst; pdoi->ppdoiPrev = ppdoiFirst; *ppdoiFirst = pdoi; _AssertDoi(pdoi, tYes); _Leave(); } /*************************************************************************** Unlink object from list. ***************************************************************************/ priv void _UnlinkDoi(DOI *pdoi) { _Enter(); _AssertDoi(pdoi, tYes); *pdoi->ppdoiPrev = pdoi->pdoiNext; if (pvNil != pdoi->pdoiNext) { pdoi->pdoiNext->ppdoiPrev = pdoi->ppdoiPrev; _AssertDoi(pdoi->pdoiNext, tYes); } pdoi->ppdoiPrev = pvNil; pdoi->pdoiNext = pvNil; _AssertDoi(pdoi, tNo); _Leave(); } /*************************************************************************** Called if anyone tries to copy a class with NOCOPY(cls) in its declaration. ***************************************************************************/ void __AssertOnCopy(void) { Bug("Copying a non-copyable object"); } /*************************************************************************** Asserts on unmarked allocated (BASE) objects. ***************************************************************************/ void AssertUnmarkedObjs(void) { _Enter(); STN stn; SZS szs; BASE *pbase; DOI *pdoi; DOI *pdoiLast; bool fAssert; long cdoiLost = 0; long lwThread = LwThreadCur(); Assert(_pdoiFirstRaw == pvNil, "Raw list is not empty!"); // we want to traverse the list in the reverse order to report problems // find the end of the list and see if there are any lost blocks pdoiLast = pvNil; for (pdoi = _pdoiFirst; pvNil != pdoi; pdoi = pdoi->pdoiNext) { pdoiLast = pdoi; if (pdoi->cactRef == 0 && pdoi->lwThread == lwThread) cdoiLost++; } if (cdoiLost == 0) { // no lost blocks goto LDone; } stn.FFormatSz(PszLit("Total lost objects: %d. Press 'Debugger' for detail"), cdoiLost); stn.GetSzs(szs); fAssert = FAssertProc(__szsFile, __LINE__, szs, pvNil, 0); for (pdoi = pdoiLast; ; ) { pbase = _PbaseFromDoi(pdoi); AssertPo(pbase, fobjAllocated); if (pdoi->cactRef == 0 && pdoi->lwThread == lwThread) { if (fAssert) { stn.FFormatSz( PszLit("\nLost object: cls='%f', size=%d, ") PszLit("StackTrace=(use map file)"), pbase->Cls(), pdoi->cbTot - size(DOI)); stn.GetSzs(szs); if (FAssertProc(pdoi->pszsFile, pdoi->lwLine, szs, pdoi->rglwStack, kclwStackDoi * size(long))) { Debugger(); } } MarkMemObj(pbase); } if (pdoi->ppdoiPrev == &_pdoiFirst) break; // UUUUGGGGH! We don't have a pointer to the previous DOI, we // have a pointer to the previous DOI's pdoiNext! pdoi = (DOI *)PvSubBv(pdoi->ppdoiPrev, offset(DOI, pdoiNext)); } LDone: _Leave(); } /*************************************************************************** Clears all marks on allocated (BASE) objects. ***************************************************************************/ void UnmarkAllObjs(void) { _Enter(); BASE *pbase; DOI *pdoi; long lwThread = LwThreadCur(); Assert(_pdoiFirstRaw == pvNil, "Raw list is not empty!"); for (pdoi = _pdoiFirst; pvNil != pdoi; pdoi = pdoi->pdoiNext) { pbase = _PbaseFromDoi(pdoi); AssertPo(pbase, fobjAllocated); if (pdoi->lwThread == lwThread) pdoi->cactRef = 0; } _Leave(); } #endif //DEBUG /*************************************************************************** Linked list element constructor ***************************************************************************/ BLL::BLL(void) { _ppbllPrev = pvNil; _pbllNext = pvNil; } /*************************************************************************** Remove the element from the linked list ***************************************************************************/ BLL::~BLL(void) { // unlink the thing if (_ppbllPrev != pvNil) _Attach(pvNil); } /*************************************************************************** Remove the element from the linked list (if it's in one) and reattach it at ppbllPrev (if not pvNil). ***************************************************************************/ void BLL::_Attach(void *ppbllPrev) { AssertThis(0); PBLL *ppbll = (PBLL *)ppbllPrev; AssertNilOrVarMem(ppbll); // unlink the thing if (_ppbllPrev != pvNil) { Assert(*_ppbllPrev == this, "links corrupt"); if ((*_ppbllPrev = _pbllNext) != pvNil) { Assert(_pbllNext->_ppbllPrev == &_pbllNext, "links corrupt 2"); _pbllNext->_ppbllPrev = _ppbllPrev; } } // link the thing if ((_ppbllPrev = ppbll) == pvNil) { // not in a linked list _pbllNext = pvNil; return; } if ((_pbllNext = *ppbll) != pvNil) { Assert(_pbllNext->_ppbllPrev == ppbll, "links corrupt 3"); _pbllNext->_ppbllPrev = &_pbllNext; } *ppbll = this; } #ifdef DEBUG /*************************************************************************** Check the links. ***************************************************************************/ void BLL::AssertValid(ulong grf) { BLL_PAR::AssertValid(grf); if (_pbllNext != pvNil) { AssertVarMem(_pbllNext); Assert(_pbllNext->_ppbllPrev == &_pbllNext, "links corrupt"); } if (_ppbllPrev != pvNil) { AssertVarMem(_ppbllPrev); Assert(*_ppbllPrev == this, "links corrupt 2"); } } #endif //DEBUG