/****************************************************************************
*
* Copyright (C) 2000-2001 RealNetworks, Inc. All rights reserved.
*
* This program is free software. It may be distributed under the terms
* in the file LICENSE, found in the top level of the source distribution.
*
*/
#include <stdarg.h>
#include "dbg.h"
#undef new
#undef delete
#ifndef NDEBUG
void dbgout( const char* fmt, ... )
{
char str[4096];
va_list v;
va_start( v, fmt );
vsprintf( str, fmt, v );
strcat( str, "\n" );
#ifdef _UNIX
fputs( str, stderr );
#endif
#ifdef _WIN32
::OutputDebugString( str );
#endif
}
/*
* Heap management using operator new/delete. These routines use unbalanced
* binary trees to keep track of allocations in an attempt to make them fast
* yet simple.
*
* The trees for scalar and vector allocations are completely separate so
* that trying to deallocate memory with the wrong version of operator
* delete will always fail.
*
* Global operator new is redefined to pass the file and line where the
* allocation occurred to facilitate memory leak debugging. Unfortunately,
* it doesn't seem to be possible to redefine operator delete.
*
* Each block of memory consists of an alloc_node header, the requested
* memory block, and two-byte guards before and after the requested memory
* block. The requested memory block is filled with a semi-random byte
* value to ensure that the caller does not rely on any particular initial
* bit pattern (eg. a block of zeros or NULLs). It is refilled with a
* (possibly different) byte value after deallocation to ensure that the
* caller doesn't attempt to use the freed memory.
*
* NOTES:
* It is perfectly valid to allocate a block of length zero. The returned
* block should have a length of 1. This simplifies algorithms, especially
* those that do vector allocation based on an unsigned input parameter. It
* is also valid to delete a NULL block. This simplifies object destruction
* by alleviating the need for if() guards. These rules have been around
* since C++ was born but most people don't seem to know or care.
*/
struct alloc_node
{
alloc_node* lptr;
alloc_node* rptr;
size_t len;
CPCHAR file;
UINT line;
};
static alloc_node* g_heap = NULL;
static alloc_node* g_vector_heap = NULL;
// Our magic guard bytes
static BYTE g_guard[] =
{
0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF,
0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF
};
void* operator new( size_t n, CPCHAR file, UINT line )
{
BYTE* pmem = NULL;
if( !n ) n = 1;
alloc_node* pnode = (alloc_node*)malloc( n + 2*sizeof(g_guard) + sizeof(alloc_node) );
if( pnode )
{
pmem = (BYTE*)pnode + sizeof(alloc_node) + sizeof(g_guard);
memcpy( pmem - sizeof(g_guard), g_guard, sizeof(g_guard) );
memset( pmem, time(NULL), n );
memcpy( pmem + n, g_guard, sizeof(g_guard) );
pnode->lptr = pnode->rptr = NULL;
pnode->len = n;
pnode->file = file;
pnode->line = line;
alloc_node** ppuplink = &g_heap;
alloc_node* pcur = g_heap;
while( pcur )
{
if( pnode == pcur )
{
dbgout( "*** FATAL: duplicate memory allocated ***" );
assert( false );
exit( -1 );
}
if( pnode < pcur )
{
ppuplink = &pcur->lptr;
pcur = pcur->lptr;
}
else
{
ppuplink = &pcur->rptr;
pcur = pcur->rptr;
}
}
*ppuplink = pnode;
}
return pmem;
}
void* operator new[]( size_t n, CPCHAR file, UINT line )
{
BYTE* pmem = NULL;
if( !n ) n = 1;
alloc_node* pnode = (alloc_node*)malloc( n + 2*sizeof(g_guard) + sizeof(alloc_node) );
if( pnode )
{
pmem = (BYTE*)pnode + sizeof(alloc_node) + sizeof(g_guard);
memcpy( pmem - sizeof(g_guard), g_guard, sizeof(g_guard) );
memset( pmem, time(NULL), n );
memcpy( pmem + n, g_guard, sizeof(g_guard) );
pnode->lptr = pnode->rptr = NULL;
pnode->len = n;
pnode->file = file;
pnode->line = line;
alloc_node** ppuplink = &g_vector_heap;
alloc_node* pcur = g_vector_heap;
while( pcur )
{
if( pnode == pcur )
{
dbgout( "*** FATAL: duplicate memory allocated ***" );
assert( false );
exit( -1 );
}
if( pnode < pcur )
{
ppuplink = &pcur->lptr;
pcur = pcur->lptr;
}
else
{
ppuplink = &pcur->rptr;
pcur = pcur->rptr;
}
}
*ppuplink = pnode;
}
return pmem;
}
void operator delete( void* p )
{
if( !p ) return;
if( !g_heap )
{
dbgout( "*** FATAL: delete with empty heap ***" );
assert( false );
exit( -1 );
}
alloc_node* pcur = g_heap;
alloc_node** ppuplink = &g_heap;
while( pcur )
{
void* pcurblk = (char*)pcur + sizeof(alloc_node) + sizeof(g_guard);
if( p == pcurblk )
{
BYTE* pmem = (BYTE*)p;
if( memcmp( pmem - sizeof(g_guard), g_guard, sizeof(g_guard) ) != 0 ||
memcmp( pmem + pcur->len, g_guard, sizeof(g_guard) ) != 0 )
{
dbgout( "*** FATAL: corrupted memory at %08X", p );
assert( false );
exit( -1 );
}
memset( pmem, time(NULL), pcur->len );
if( pcur->lptr && pcur->rptr )
{
// node has both ptrs so replace it with left child and move
// right child to bottom right of left child's tree
alloc_node* pend = pcur->lptr;
while( pend->rptr ) pend = pend->rptr;
*ppuplink = pcur->lptr;
pend->rptr = pcur->rptr;
}
else
{
// move child up
*ppuplink = (pcur->lptr) ? pcur->lptr : pcur->rptr;
}
free( pcur );
return;
}
if( p < pcurblk )
{
ppuplink = &pcur->lptr;
pcur = pcur->lptr;
}
else
{
ppuplink = &pcur->rptr;
pcur = pcur->rptr;
}
}
dbgout( "*** FATAL: delete on unalloced memory ***" );
assert( false );
exit( -1 );
}
void operator delete[]( void* p )
{
if( !p ) return;
if( !g_vector_heap )
{
dbgout( "*** FATAL: delete with empty heap ***" );
assert( false );
exit( -1 );
}
alloc_node* pcur = g_vector_heap;
alloc_node** ppuplink = &g_vector_heap;
while( pcur )
{
void* pcurblk = (char*)pcur + sizeof(alloc_node) + sizeof(g_guard);
if( p == pcurblk )
{
BYTE* pmem = (BYTE*)p;
if( memcmp( pmem - sizeof(g_guard), g_guard, sizeof(g_guard) ) != 0 ||
memcmp( pmem + pcur->len, g_guard, sizeof(g_guard) ) != 0 )
{
dbgout( "*** FATAL: corrupted memory at %08X", p );
assert( false );
exit( -1 );
}
memset( pmem, time(NULL), pcur->len );
if( pcur->lptr && pcur->rptr )
{
// node has both ptrs so replace it with left child and move
// right child to bottom right of left child's tree
alloc_node* pend = pcur->lptr;
while( pend->rptr ) pend = pend->rptr;
*ppuplink = pcur->lptr;
pend->rptr = pcur->rptr;
}
else
{
// move child up
*ppuplink = (pcur->lptr) ? pcur->lptr : pcur->rptr;
}
free( pcur );
return;
}
if( p < pcurblk )
{
ppuplink = &pcur->lptr;
pcur = pcur->lptr;
}
else
{
ppuplink = &pcur->rptr;
pcur = pcur->rptr;
}
}
dbgout( "*** FATAL: delete on unalloced memory ***" );
assert( false );
exit( -1 );
}
void* operator new( size_t n )
{
return ::operator new( n, "(unknown)", 0 );
}
void* operator new[]( size_t n )
{
return ::operator new[]( n, "(unknown)", 0 );
}
static void walk_alloc_tree( alloc_node* pcur, size_t* pttl )
{
if( pcur )
{
walk_alloc_tree( pcur->lptr, pttl );
dbgout( "%s(%u): %u bytes at %08X", pcur->file, pcur->line,
pcur->len, (char*)pcur + sizeof(alloc_node) );
*pttl += pcur->len;
walk_alloc_tree( pcur->rptr, pttl );
}
}
void dump_alloc_heaps( void )
{
if( g_heap || g_vector_heap )
{
size_t ttl = 0;
dbgout( "Memory leaks detected" );
dbgout( "=====================" );
dbgout( "" );
if( g_heap )
{
dbgout( "Scalar objects" );
dbgout( "--------------" );
walk_alloc_tree( g_heap, &ttl );
dbgout( "" );
}
if( g_vector_heap )
{
dbgout( "Vector objects" );
dbgout( "--------------" );
walk_alloc_tree( g_vector_heap, &ttl );
dbgout( "" );
}
dbgout( "=====================" );
dbgout( "Total bytes: %u", ttl );
dbgout( "=====================" );
}
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1