/* Copyright (c) 2010 Wildfire Games
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "precompiled.h"
#include "lib/sysdep/os/win/wdbg_heap.h"

#include "lib/sysdep/os/win/win.h"
#include <crtdbg.h>
#include <excpt.h>

#include "lib/external_libraries/dbghelp.h"
#include "lib/sysdep/cpu.h"	// cpu_AtomicAdd
#include "lib/sysdep/os/win/winit.h"
#include "lib/sysdep/os/win/wdbg.h"       // wdbg_printf
#include "lib/sysdep/os/win/wdbg_sym.h"   // wdbg_sym_WalkStack


WINIT_REGISTER_EARLY_INIT2(wdbg_heap_Init);	// wutil -> wdbg_heap
WINIT_REGISTER_LATE_SHUTDOWN2(wdbg_heap_Shutdown);	// last - no leaks are detected after this


void wdbg_heap_Enable(bool enable)
{
#ifdef _DEBUG	// (avoid "expression has no effect" warning in release builds)
	int flags = 0;
	if(enable)
	{
		flags |= _CRTDBG_ALLOC_MEM_DF;	// enable checks at deallocation time
		flags |= _CRTDBG_LEAK_CHECK_DF;	// report leaks at exit
#if 0
		flags |= _CRTDBG_CHECK_ALWAYS_DF;	// check during every heap operation (too slow to be practical)
		flags |= _CRTDBG_DELAY_FREE_MEM_DF;	// memory is never actually freed
#endif
	}
	_CrtSetDbgFlag(flags);

	// Send output to stdout as well as the debug window, so it works during
	// the normal build process as well as when debugging the test .exe
	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
	_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
#else
	UNUSED2(enable);
#endif
}


void wdbg_heap_Validate()
{
	int ok = TRUE;
	__try
	{
		// NB: this is a no-op if !_CRTDBG_ALLOC_MEM_DF.
		// we could call _heapchk but that would catch fewer errors.
		ok = _CrtCheckMemory();
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		ok = FALSE;
	}

	wdbg_assert(ok == TRUE);	// else: heap is corrupt!
}


//-----------------------------------------------------------------------------
// improved leak detection
//-----------------------------------------------------------------------------

// (this relies on the debug CRT; not compiling it at all in release builds
// avoids unreferenced local function warnings)
// (this has only been tested on IA32 and seems to have trouble with larger
// pointers and is horribly expensive, so it's disabled for now.)
#if !defined(NDEBUG) && ARCH_IA32 && 0
# define ENABLE_LEAK_INSTRUMENTATION 1
#else
# define ENABLE_LEAK_INSTRUMENTATION 0
#endif

#if ENABLE_LEAK_INSTRUMENTATION

// leak detectors often rely on macro redirection to determine the file and
// line of allocation owners (see _CRTDBG_MAP_ALLOC). unfortunately this
// breaks code that uses placement new or functions called free() etc.
//
// we avoid this problem by using stack traces. this implementation differs
// from other approaches, e.g. Visual Leak Detector (the safer variant
// before DLL hooking was used) in that no auxiliary storage is needed.
// instead, the trace is stashed within the memory block header.
//
// to avoid duplication of effort, the CRT's leak detection code is not
// modified; we only need an allocation and report hook. the latter
// mixes the improved file/line information into the normal report.


//-----------------------------------------------------------------------------
// memory block header

// the one disadvantage of our approach is that it requires knowledge of
// the internal memory block header structure. it is hoped that IsValid will
// uncover any changes. the following definition was adapted from dbgint.h:
struct _CrtMemBlockHeader
{
	struct _CrtMemBlockHeader* next;
	struct _CrtMemBlockHeader* prev;
	char* file;
	int line;
	// fields reversed on Win64 to ensure size % 16 == 0
#if OS_WIN64
	int blockType;
	size_t userDataSize;
#else
	size_t userDataSize;
	int blockType;
#endif
	long allocationNumber;
	u8 gap[4];

	bool IsValid() const
	{
		__try
		{
			if(prev && prev->next != this)
				return false;
			if(next && next->prev != this)
				return false;
			if((unsigned)blockType > 4)
				return false;
			if(userDataSize > 1*GiB)
				return false;
			if(allocationNumber == 0)
				return false;
			for(int i = 0; i < 4; i++)
			{
				if(gap[i] != 0xFD)
					return false;
			}

			// this is a false alarm if there is exactly one extant allocation,
			// but also a valuable indication of a block that has been removed
			// from the list (i.e. freed).
			if(prev == next)
				return false;
		}
		__except(EXCEPTION_EXECUTE_HANDLER)
		{
			return false;
		}

		return true;
	}
};

static _CrtMemBlockHeader* HeaderFromData(void* userData)
{
	_CrtMemBlockHeader* const header = ((_CrtMemBlockHeader*)userData)-1;
	wdbg_assert(header->IsValid());
	return header;
}


/**
 * update our idea of the head of the linked list of heap blocks.
 * called from the allocation hook (see explanation there)
 *
 * @return the current head (most recent allocation).
 * @param operation the current heap operation
 * @param userData allocation address (if reallocating or deallocating)
 * @param hasChanged a convenient indication of whether the return value is
 * different than that of the last call.
 **/
static _CrtMemBlockHeader* GetHeapListHead(int operation, void* userData, bool& hasChanged)
{
	static _CrtMemBlockHeader* s_heapListHead;

	// first call: get the heap block list head
	// notes:
	// - there is no O(1) accessor for this, so we maintain a copy.
	// - must be done here instead of in an initializer to guarantee
	//   consistency, since we are now under the _HEAP_LOCK.
	if(!s_heapListHead)
	{
		_CrtMemState state = {0};
		_CrtMemCheckpoint(&state);	// O(N)
		s_heapListHead = state.pBlockHeader;
		wdbg_assert(s_heapListHead->IsValid());
	}

	// the last operation was an allocation or expanding reallocation;
	// exactly one block has been prepended to the list.
	if(s_heapListHead->prev)
	{
		s_heapListHead = s_heapListHead->prev;	// set to new head of list
		wdbg_assert(s_heapListHead->IsValid());
		wdbg_assert(s_heapListHead->prev == 0);
		hasChanged = true;
	}
	// the list head remained unchanged, so the last operation was a
	// non-expanding reallocation or free.
	else
		hasChanged = false;

	// special case: handle invalidation of the list head
	// note: even shrinking reallocations cause deallocation.
	if(operation != _HOOK_ALLOC && userData == s_heapListHead+1)
	{
		s_heapListHead = s_heapListHead->next;
		wdbg_assert(s_heapListHead->IsValid());

		hasChanged = false;	// (head is now the same as last time)
	}

	return s_heapListHead;
}


//-----------------------------------------------------------------------------
// call stack filter

// we need to make the most out of the limited amount of frames. to that end,
// only user functions are stored; we skip known library and helper functions.
// these are determined by recording frames encountered in a backtrace.

/**
 * extents of a module in memory; used to ignore callers that lie within
 * the C runtime library.
 **/
class ModuleExtents
{
public:
	ModuleExtents()
		: m_address(0), m_length(0)
	{
	}

	ModuleExtents(const wchar_t* dllName)
	{
		HMODULE hModule = GetModuleHandleW(dllName);
		PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((u8*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew);
		m_address = (uintptr_t)hModule + ntHeaders->OptionalHeader.BaseOfCode;
		MEMORY_BASIC_INFORMATION mbi = {0};
		VirtualQuery((void*)m_address, &mbi, sizeof(mbi));
		m_length = mbi.RegionSize;
	}

	uintptr_t Address() const
	{
		return m_address;
	}

	uintptr_t Length() const
	{
		return m_length;
	}

	bool Contains(uintptr_t address) const
	{
		return (address - m_address) < m_length;
	}

private:
	uintptr_t m_address;
	size_t m_length;
};


/**
 * set data structure that avoids dynamic allocations because they would
 * cause the allocation hook to be reentered (bad).
 **/
template<typename T, size_t maxItems>
class ArraySet
{
public:
	ArraySet()
	{
		m_arrayEnd = m_array;
	}

	void Add(T t)
	{
		if(m_arrayEnd == m_array+maxItems)
		{
			RemoveDuplicates();
			wdbg_assert(m_arrayEnd < m_array+maxItems);
		}
		*m_arrayEnd++ = t;
	}

	bool Find(T t) const
	{
		return std::find(m_array, const_cast<const T*>(m_arrayEnd), t) != m_arrayEnd;
	}

	void RemoveDuplicates()
	{
		std::sort(m_array, m_arrayEnd);
		m_arrayEnd = std::unique(m_array, m_arrayEnd);
	}

private:
	T m_array[maxItems];
	T* m_arrayEnd;
};


class CallerFilter
{
public:
	CallerFilter()
	{
		AddRuntimeLibraryToIgnoreList();

		m_isRecordingKnownCallers = true;
		CallHeapFunctions();
		m_isRecordingKnownCallers = false;
		m_knownCallers.RemoveDuplicates();
	}

	Status NotifyOfCaller(uintptr_t pc)
	{
		if(!m_isRecordingKnownCallers)
			return INFO::SKIPPED;

		// last 'known' function has been reached
		if(pc == (uintptr_t)&CallerFilter::CallHeapFunctions)
			return INFO::ALL_COMPLETE;

		// pc is a 'known' function on the allocation hook's back-trace
		// (e.g. _malloc_dbg and other helper functions)
		m_knownCallers.Add(pc);
		return INFO::OK;
	}

	bool IsKnownCaller(uintptr_t pc) const
	{
		for(size_t i = 0; i < numModules; i++)
		{
			if(m_moduleIgnoreList[i].Contains(pc))
				return true;
		}

		return m_knownCallers.Find(pc);
	}

private:
	static const size_t numModules = 2;

	void AddRuntimeLibraryToIgnoreList()
	{
#if MSC_VERSION && _DLL	// DLL runtime library
#ifdef NDEBUG
		static const wchar_t* dllNameFormat = L"msvc%c%d" L".dll";
#else
		static const wchar_t* dllNameFormat = L"msvc%c%d" L"d" L".dll";
#endif
		const int dllVersion = (MSC_VERSION-600)/10;	// VC2005: 1400 => 80
		wdbg_assert(0 < dllVersion && dllVersion <= 999);
		for(int i = 0; i < numModules; i++)
		{
			static const char modules[numModules] = { 'r', 'p' };	// C and C++ runtime libraries
			wchar_t dllName[20];
			swprintf_s(dllName, ARRAY_SIZE(dllName), dllNameFormat, modules[i], dllVersion);
			m_moduleIgnoreList[i] = ModuleExtents(dllName);
		}
#endif
	}

	static void CallHeapFunctions()
	{
		{
			void* p1 = malloc(1);
			void* p2 = realloc(p1, 111);
			if(p2)
				free(p2);
			else
				free(p1);
		}
		{
			u8* p = new u8;
			delete p;
		}
		{
			u8* p = new u8[2];
			delete[] p;
		}
	}

	ModuleExtents m_moduleIgnoreList[numModules];

	// note: this mechanism cannot hope to exclude every single STL helper
	// function, which is why we need the module ignore list.
	// however, it is still useful when compiling against the static CRT.
	bool m_isRecordingKnownCallers;
	ArraySet<uintptr_t, 500> m_knownCallers;
};


//-----------------------------------------------------------------------------
// stash (part of) a stack trace within _CrtMemBlockHeader

// this avoids the need for a mapping between allocation number and the
// caller information, which is slow, requires locking and consumes memory.
//
// callers := array of addresses inside functions that constitute the
// stack back-trace.

static const size_t numQuantizedPcBits = sizeof(uintptr_t)*CHAR_BIT - 2;

static uintptr_t Quantize(uintptr_t pc)
{
	// postcondition: the return value lies within the same function as
	// pc but can be stored in fewer bits. this is possible because:
	// - linkers typically align functions to at least four bytes
	// - pc is a return address and thus preceded by a call instruction and
	//   function prolog, which requires at least four bytes.
	return pc/4;
}

static uintptr_t Expand(uintptr_t pc)
{
	return pc*4;
}


static const size_t numEncodedLengthBits = 2;
static const size_t maxCallers = (sizeof(char*)+sizeof(int))*CHAR_BIT / (2+14);

static size_t NumBitsForEncodedLength(size_t encodedLength)
{
	static const size_t numBitsForEncodedLength[1u << numEncodedLengthBits] =
	{
		8,	// 1K
		14,	// 64K
		20,	// 4M
		numQuantizedPcBits	// a full pointer
	};
	return numBitsForEncodedLength[encodedLength];
}

static size_t EncodedLength(uintptr_t quantizedOffset)
{
	for(size_t encodedLength = 0; encodedLength < 1u << numEncodedLengthBits; encodedLength++)
	{
		const size_t numBits = NumBitsForEncodedLength(encodedLength);
		const uintptr_t maxValue = (1u << numBits)-1;
		if(quantizedOffset <= maxValue)
			return encodedLength;
	}

	wdbg_assert(0);	// unreachable
	return 0;
}


static uintptr_t codeSegmentAddress;
static uintptr_t quantizedCodeSegmentAddress;
static uintptr_t quantizedCodeSegmentLength;

static void FindCodeSegment()
{
	const wchar_t* dllName = 0;	// current module
	ModuleExtents extents(dllName);
	codeSegmentAddress = extents.Address();
	quantizedCodeSegmentAddress = Quantize(codeSegmentAddress);
	quantizedCodeSegmentLength = Quantize(extents.Length());
}


class BitStream
{
public:
	BitStream(u8* storage, size_t storageSize)
		: m_remainderBits(0), m_numRemainderBits(0)
		, m_pos(storage), m_bitsLeft((size_t)storageSize*8)
	{
	}

	size_t BitsLeft() const
	{
		return m_bitsLeft;
	}

	void Write(const size_t numOutputBits, uintptr_t outputValue)
	{
		wdbg_assert(numOutputBits <= m_bitsLeft);
		wdbg_assert(outputValue < ((uintptr_t)1u << numOutputBits));

		size_t outputBitsLeft = numOutputBits;
		while(outputBitsLeft > 0)
		{
			const size_t numBits = std::min(outputBitsLeft, size_t(8));
			m_bitsLeft -= numBits;

			// (NB: there is no need to extract exactly numBits because
			// outputValue's MSBs were verified to be zero)
			const uintptr_t outputByte = outputValue & 0xFF;
			outputValue >>= 8;
			outputBitsLeft -= numBits;

			m_remainderBits |= outputByte << m_numRemainderBits;
			m_numRemainderBits += numBits;
			if(m_numRemainderBits >= 8)
			{
				const u8 remainderByte = (m_remainderBits & 0xFF);
				m_remainderBits >>= 8;
				m_numRemainderBits -= 8;

				*m_pos++ = remainderByte;
			}
		}
	}

	void Finish()
	{
		const size_t partialBits = m_numRemainderBits % 8;
		if(partialBits)
		{
			m_bitsLeft -= 8-partialBits;
			m_numRemainderBits += 8-partialBits;
		}
		while(m_numRemainderBits)
		{
			const u8 remainderByte = (m_remainderBits & 0xFF);
			*m_pos++ = remainderByte;
			m_remainderBits >>= 8;
			m_numRemainderBits -= 8;
		}

		wdbg_assert(m_bitsLeft % 8 == 0);
		while(m_bitsLeft)
		{
			*m_pos++ = 0;
			m_bitsLeft -= 8;
		}
	}

	uintptr_t Read(const size_t numInputBits)
	{
		wdbg_assert(numInputBits <= m_bitsLeft);

		uintptr_t inputValue = 0;
		size_t inputBitsLeft = numInputBits;
		while(inputBitsLeft > 0)
		{
			const size_t numBits = std::min(inputBitsLeft, size_t(8));
			m_bitsLeft -= numBits;

			if(m_numRemainderBits < numBits)
			{
				const size_t inputByte = *m_pos++;
				m_remainderBits |= inputByte << m_numRemainderBits;
				m_numRemainderBits += 8;
			}

			const uintptr_t remainderByte = (m_remainderBits & ((1u << numBits)-1));
			m_remainderBits >>= numBits;
			m_numRemainderBits -= numBits;
			inputValue |= remainderByte << (numInputBits-inputBitsLeft);

			inputBitsLeft -= numBits;
		}

		return inputValue;
	}

private:
	uintptr_t m_remainderBits;
	size_t m_numRemainderBits;
	u8* m_pos;
	size_t m_bitsLeft;
};


static void StashCallers(_CrtMemBlockHeader* header, const uintptr_t* callers, size_t numCallers)
{
	// transform an array of callers into a (sorted and unique) set.
	uintptr_t quantizedPcSet[maxCallers];
	std::transform(callers, callers+numCallers, quantizedPcSet, Quantize);
	std::sort(quantizedPcSet, quantizedPcSet+numCallers);
	uintptr_t* const end = std::unique(quantizedPcSet, quantizedPcSet+numCallers);
	const size_t quantizedPcSetSize = end-quantizedPcSet;

	// transform the set into a sequence of quantized offsets.
	uintptr_t quantizedOffsets[maxCallers];
	if(quantizedPcSet[0] >= quantizedCodeSegmentAddress)
		quantizedOffsets[0] = quantizedPcSet[0] - quantizedCodeSegmentAddress;
	else
	{
		quantizedOffsets[0] = quantizedPcSet[0];

		// make sure RetrieveCallers can differentiate between pointers and code-segment-offsets
		wdbg_assert(quantizedOffsets[0] >= quantizedCodeSegmentLength);
	}
	for(size_t i = 1; i < numCallers; i++)
		quantizedOffsets[i] = quantizedPcSet[i] - quantizedPcSet[i-1];

	// write quantized offsets to stream
	BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line));
	for(size_t i = 0; i < quantizedPcSetSize; i++)
	{
		const uintptr_t quantizedOffset = quantizedOffsets[i];
		const size_t encodedLength = EncodedLength(quantizedOffset);
		const size_t numBits = NumBitsForEncodedLength(encodedLength);
		if(bitStream.BitsLeft() < numEncodedLengthBits+numBits)
			break;
		bitStream.Write(numEncodedLengthBits, encodedLength);
		bitStream.Write(numBits, quantizedOffset);
	}

	bitStream.Finish();
}


static void RetrieveCallers(_CrtMemBlockHeader* header, uintptr_t* callers, size_t& numCallers)
{
	// read quantized offsets from stream
	uintptr_t quantizedOffsets[maxCallers];
	numCallers = 0;
	BitStream bitStream((u8*)&header->file, sizeof(header->file)+sizeof(header->line));
	for(;;)
	{
		if(bitStream.BitsLeft() < numEncodedLengthBits)
			break;
		const size_t encodedLength = bitStream.Read(numEncodedLengthBits);
		const size_t numBits = NumBitsForEncodedLength(encodedLength);
		if(bitStream.BitsLeft() < numBits)
			break;
		const uintptr_t quantizedOffset = bitStream.Read(numBits);
		if(!quantizedOffset)
			break;
		quantizedOffsets[numCallers++] = quantizedOffset;
	}

	if(!numCallers)
		return;

	// expand offsets into a set of callers
	if(quantizedOffsets[0] <= quantizedCodeSegmentLength)
		callers[0] = Expand(quantizedOffsets[0] + quantizedCodeSegmentAddress);
	else
		callers[0] = Expand(quantizedOffsets[0]);
	for(size_t i = 1; i < numCallers; i++)
		callers[i] = callers[i-1] + Expand(quantizedOffsets[i]);
}


//-----------------------------------------------------------------------------
// find out who called an allocation function

/**
 * gather and store a (filtered) list of callers.
 **/
class CallStack
{
public:
	void Gather()
	{
		m_numCallers = 0;
		CONTEXT context;
		(void)debug_CaptureContext(&context);
		(void)wdbg_sym_WalkStack(OnFrame_Trampoline, (uintptr_t)this, context);
		std::fill(m_callers+m_numCallers, m_callers+maxCallers, 0);
	}

	const uintptr_t* Callers() const
	{
		return m_callers;
	}

	size_t NumCallers() const
	{
		return m_numCallers;
	}

private:
	Status OnFrame(const STACKFRAME64* frame)
	{
		const uintptr_t pc = frame->AddrPC.Offset;

		// skip invalid frames
		if(pc == 0)
			return INFO::OK;

		Status ret = m_filter.NotifyOfCaller(pc);
		// (CallerFilter provokes stack traces of heap functions; if that is
		// what happened, then we must not continue)
		if(ret != INFO::SKIPPED)
			return ret;

		// stop the stack walk if frame storage is full
		if(m_numCallers >= maxCallers)
			return INFO::ALL_COMPLETE;

		if(!m_filter.IsKnownCaller(pc))
			m_callers[m_numCallers++] = pc;
		return INFO::OK;
	}

	static Status OnFrame_Trampoline(const STACKFRAME64* frame, uintptr_t cbData)
	{
		CallStack* this_ = (CallStack*)cbData;
		return this_->OnFrame(frame);
	}

	CallerFilter m_filter;

	uintptr_t m_callers[maxCallers];
	size_t m_numCallers;
};


//-----------------------------------------------------------------------------
// RAII wrapper for installing a CRT allocation hook

class AllocationHook
{
public:
	AllocationHook()
	{
		wdbg_assert(s_instance == 0 && s_previousHook == 0);
		s_instance = this;
		s_previousHook = _CrtSetAllocHook(Hook);
	}

	~AllocationHook()
	{
		_CRT_ALLOC_HOOK removedHook = _CrtSetAllocHook(s_previousHook);
		wdbg_assert(removedHook == Hook);	// warn if we removed someone else's hook
		s_instance = 0;
		s_previousHook = 0;
	}

	/**
	 * @param operation either _HOOK_ALLOC, _HOOK_REALLOC or _HOOK_FREE
	 * @param userData is only valid (nonzero) for realloc and free because
	 * we are called BEFORE the actual heap operation.
	 **/
	virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber) = 0;

private:
	static int __cdecl Hook(int operation, void* userData, size_t size, int blockType, long allocationNumber, const unsigned char* file, int line)
	{
		static bool busy = false;
		wdbg_assert(!busy);
		busy = true;
		s_instance->OnHeapOperation(operation, userData, size, allocationNumber);
		busy = false;

		if(s_previousHook)
			return s_previousHook(operation, userData, size, blockType, allocationNumber, file, line);
		return 1;	// continue as if the hook had never been called
	}

	// unfortunately static because we can't pass our `this' pointer through
	// the allocation hook.
	static AllocationHook* s_instance;
	static _CRT_ALLOC_HOOK s_previousHook;
};

AllocationHook* AllocationHook::s_instance;
_CRT_ALLOC_HOOK AllocationHook::s_previousHook;


//-----------------------------------------------------------------------------
// our allocation hook

// ideally we would just stash the callers in the newly created header.
// unfortunately we are called BEFORE it (and the allocation) are actually
// created, so we need to keep the information around until the next call to
// AllocHook; only then can it be stored.
//
// unfortunately the CRT does not provide an O(1) means of getting at the
// most recent block header. instead, we do so once and then keep it
// up-to-date in the allocation hook. this is safe because we run under
// the _HEAP_LOCK and ensure the allocation numbers match.

static intptr_t s_numAllocations;

intptr_t wdbg_heap_NumberOfAllocations()
{
	return s_numAllocations;
}

class AllocationTracker : public AllocationHook
{
public:
	AllocationTracker()
		: m_pendingAllocationNumber(0)
	{
	}

	virtual void OnHeapOperation(int operation, void* userData, size_t size, long allocationNumber)
	{
		UNUSED2(size);

		if(operation == _HOOK_ALLOC || operation == _HOOK_REALLOC)
			cpu_AtomicAdd(&s_numAllocations, 1);

		bool hasChanged;
		_CrtMemBlockHeader* head = GetHeapListHead(operation, userData, hasChanged);
		// if the head changed, the last operation was a (re)allocation and
		// we now have its header; stash the pending call stack there.
		if(hasChanged)
		{
			wdbg_assert(head->allocationNumber == m_pendingAllocationNumber);

			// note: overwrite existing file/line info (even if valid) to avoid
			// special cases in the report hook.
			StashCallers(head, m_pendingCallStack.Callers(), m_pendingCallStack.NumCallers());
		}

		// remember the current caller for next time
		m_pendingCallStack.Gather();	// NB: called for each operation, as required by the filter recording step
		m_pendingAllocationNumber = allocationNumber;
	}

private:
	long m_pendingAllocationNumber;
	CallStack m_pendingCallStack;
};


//-----------------------------------------------------------------------------

static void PrintCallStack(const uintptr_t* callers, size_t numCallers)
{
	if(!numCallers || callers[0] == 0)
	{
		wdbg_printf(L"\n  call stack not available.\n");
		return;
	}

	wdbg_printf(L"\n  partial, unordered call stack:\n");
	for(size_t i = 0; i < numCallers; i++)
	{
		wchar_t name[DEBUG_SYMBOL_CHARS] = {'\0'}; wchar_t file[DEBUG_FILE_CHARS] = {'\0'}; int line = -1;
		Status err = debug_ResolveSymbol((void*)callers[i], name, file, &line);
		wdbg_printf(L"    ");
		if(err != INFO::OK)
			wdbg_printf(L"(error %d resolving PC=%p) ", err, callers[i]);
		if(file[0] != '\0')
			wdbg_printf(L"%ls(%d) : ", file, line);
		wdbg_printf(L"%ls\n", name);
	}
}

static int __cdecl ReportHook(int reportType, wchar_t* message, int* out)
{
	UNUSED2(reportType);

	// set up return values to reduce the chance of mistakes below
	*out = 0;	// alternatives are failure (-1) and breakIntoDebugger (1)
	const int ret = 0;	// not "handled", continue calling other hooks

	// note: this hook is transparent in that it never affects the CRT.
	// we can't suppress parts of a leak report because that causes the
	// rest of it to be skipped.

	static enum
	{
		WaitingForDump,
		WaitingForBlock,
		IsBlock
	}
	state = WaitingForDump;
	switch(state)
	{
	case WaitingForDump:
		if(!wcscmp(message, L"Dumping objects ->\n"))
			state = WaitingForBlock;
		return ret;

	case IsBlock:
		{
			// common case: "normal block at 0xPPPPPPPP, N bytes long".
			const wchar_t* addressString = wcsstr(message, L"0x");
			if(addressString)
			{
				const uintptr_t address = wcstoul(addressString, 0, 0);
				_CrtMemBlockHeader* header = HeaderFromData((void*)address);
				uintptr_t callers[maxCallers]; size_t numCallers;
				RetrieveCallers(header, callers, numCallers);
				PrintCallStack(callers, numCallers);

				state = WaitingForBlock;
				return ret;
			}
			// else: for reasons unknown, there's apparently no information
			// about the block; fall through to the previous state.
		}

	case WaitingForBlock:
		if(message[0] == '{')
			state = IsBlock;
		// suppress messages containing "file" and "line" since the normal
		// interpretation of those header fields is invalid.
		else if(wcschr(message, '('))
			message[0] = '\0';
		return ret;

	default:
		wdbg_assert(0);	// unreachable
	}

	wdbg_assert(0);	// unreachable
	return 0;
}

#else

intptr_t wdbg_heap_NumberOfAllocations()
{
	return 0;
}

#endif

//-----------------------------------------------------------------------------

#if ENABLE_LEAK_INSTRUMENTATION
static AllocationTracker* s_tracker;
#endif

static Status wdbg_heap_Init()
{
#if ENABLE_LEAK_INSTRUMENTATION
	FindCodeSegment();

	// load symbol information now (fails if it happens during shutdown)
	wchar_t name[DEBUG_SYMBOL_CHARS]; wchar_t file[DEBUG_FILE_CHARS]; int line;
	(void)debug_ResolveSymbol(wdbg_heap_Init, name, file, &line);

	int ret = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, ReportHook);
	if(ret == -1)
		abort();

	s_tracker = new AllocationTracker;
#endif

	wdbg_heap_Enable(true);

	return INFO::OK;
}

static Status wdbg_heap_Shutdown()
{
#if ENABLE_LEAK_INSTRUMENTATION
	SAFE_DELETE(s_tracker);
#endif

	return INFO::OK;
}
