/***************************************************************************/ /* */ /* ftdbgmem.c */ /* */ /* Memory debugger (body). */ /* */ /* Copyright 2001, 2002 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #include #include FT_CONFIG_CONFIG_H #include FT_INTERNAL_DEBUG_H #include FT_INTERNAL_MEMORY_H #include FT_SYSTEM_H #include FT_ERRORS_H #include FT_TYPES_H #ifdef FT_DEBUG_MEMORY #include #include typedef struct FT_MemNodeRec_* FT_MemNode; typedef struct FT_MemTableRec_* FT_MemTable; #define FT_MEM_VAL( addr ) ((FT_ULong)(FT_Pointer)( addr )) typedef struct FT_MemNodeRec_ { FT_Byte* address; FT_Long size; /* < 0 if the block was freed */ const char* alloc_file_name; FT_Long alloc_line_no; const char* free_file_name; FT_Long free_line_no; FT_MemNode link; } FT_MemNodeRec; typedef struct FT_MemTableRec_ { FT_ULong size; FT_ULong nodes; FT_MemNode* buckets; FT_ULong alloc_total; FT_ULong alloc_current; FT_ULong alloc_max; const char* file_name; FT_Long line_no; FT_Memory memory; FT_Pointer memory_user; FT_Alloc_Func alloc; FT_Free_Func free; FT_Realloc_Func realloc; } FT_MemTableRec; #define FT_MEM_SIZE_MIN 7 #define FT_MEM_SIZE_MAX 13845163 #define FT_FILENAME( x ) ((x) ? (x) : "unknown file") static const FT_UInt ft_mem_primes[] = { 7, 11, 19, 37, 73, 109, 163, 251, 367, 557, 823, 1237, 1861, 2777, 4177, 6247, 9371, 14057, 21089, 31627, 47431, 71143, 106721, 160073, 240101, 360163, 540217, 810343, 1215497, 1823231, 2734867, 4102283, 6153409, 9230113, 13845163, }; extern void ft_mem_debug_panic( const char* fmt, ... ) { va_list ap; printf( "FreeType.Debug: " ); va_start( ap, fmt ); vprintf( fmt, ap ); va_end( ap ); printf( "\n" ); exit( EXIT_FAILURE ); } static FT_ULong ft_mem_closest_prime( FT_ULong num ) { FT_UInt i; for ( i = 0; i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ ) if ( ft_mem_primes[i] > num ) return ft_mem_primes[i]; return FT_MEM_SIZE_MAX; } static FT_Pointer ft_mem_table_alloc( FT_MemTable table, FT_Long size ) { FT_Memory memory = table->memory; FT_Pointer block; memory->user = table->memory_user; block = table->alloc( memory, size ); memory->user = table; return block; } static void ft_mem_table_free( FT_MemTable table, FT_Pointer block ) { FT_Memory memory = table->memory; memory->user = table->memory_user; table->free( memory, block ); memory->user = table; } static void ft_mem_table_resize( FT_MemTable table ) { FT_ULong new_size; new_size = ft_mem_closest_prime( table->nodes ); if ( new_size != table->size ) { FT_MemNode* new_buckets ; FT_ULong i; new_buckets = (FT_MemNode *) ft_mem_table_alloc( table, new_size * sizeof ( FT_MemNode ) ); if ( new_buckets == NULL ) return; FT_MEM_ZERO( new_buckets, sizeof ( FT_MemNode ) * new_size ); for ( i = 0; i < table->size; i++ ) { FT_MemNode node, next, *pnode; FT_ULong hash; node = table->buckets[i]; while ( node ) { next = node->link; hash = FT_MEM_VAL( node->address ) % new_size; pnode = new_buckets + hash; node->link = pnode[0]; pnode[0] = node; node = next; } } if ( table->buckets ) ft_mem_table_free( table, table->buckets ); table->buckets = new_buckets; table->size = new_size; } } static FT_MemTable ft_mem_table_new( FT_Memory memory ) { FT_MemTable table; table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) ); if ( table == NULL ) goto Exit; FT_MEM_ZERO( table, sizeof ( *table ) ); table->size = FT_MEM_SIZE_MIN; table->nodes = 0; table->memory = memory; table->memory_user = memory->user; table->alloc = memory->alloc; table->realloc = memory->realloc; table->free = memory->free; table->buckets = (FT_MemNode *) memory->alloc( memory, table->size * sizeof ( FT_MemNode ) ); if ( table->buckets ) FT_MEM_ZERO( table->buckets, sizeof ( FT_MemNode ) * table->size ); else { memory->free( memory, table ); table = NULL; } Exit: return table; } static void ft_mem_table_destroy( FT_MemTable table ) { FT_ULong i; if ( table ) { FT_Long leak_count = 0; FT_ULong leaks = 0; for ( i = 0; i < table->size; i++ ) { FT_MemNode *pnode = table->buckets + i, next, node = *pnode; while ( node ) { next = node->link; node->link = 0; if ( node->size > 0 ) { printf( "leaked memory block at address %p, size %8ld in (%s:%ld)\n", node->address, node->size, FT_FILENAME( node->alloc_file_name ), node->alloc_line_no ); leak_count++; leaks += node->size; ft_mem_table_free( table, node->address ); } node->address = NULL; node->size = 0; free( node ); node = next; } table->buckets[i] = 0; } ft_mem_table_free( table, table->buckets ); table->buckets = NULL; table->size = 0; table->nodes = 0; printf( "FreeType: total memory allocations = %ld\n", table->alloc_total ); printf( "FreeType: maximum memory footprint = %ld\n", table->alloc_max ); free( table ); if ( leak_count > 0 ) ft_mem_debug_panic( "FreeType: %ld bytes of memory leaked in %ld blocks\n", leaks, leak_count ); printf( "FreeType: No memory leaks detected!\n" ); } } static FT_MemNode* ft_mem_table_get_nodep( FT_MemTable table, FT_Byte* address ) { FT_ULong hash; FT_MemNode *pnode, node; hash = FT_MEM_VAL( address ); pnode = table->buckets + ( hash % table->size ); for (;;) { node = pnode[0]; if ( !node ) break; if ( node->address == address ) break; pnode = &node->link; } return pnode; } static void ft_mem_table_set( FT_MemTable table, FT_Byte* address, FT_ULong size ) { FT_MemNode *pnode, node; if ( table ) { pnode = ft_mem_table_get_nodep( table, address ); node = *pnode; if ( node ) { if ( node->size < 0 ) { /* this block was already freed. This means that our memory is */ /* now completely corrupted! */ ft_mem_debug_panic( "memory heap corrupted (allocating freed block)" ); } else { /* this block was already allocated. This means that our memory */ /* is also corrupted! */ ft_mem_debug_panic( "memory heap corrupted (re-allocating allocated block)" ); } } /* we need to create a new node in this table */ node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) ); if ( node == NULL ) ft_mem_debug_panic( "not enough memory to run memory tests" ); node->address = address; node->size = size; node->alloc_file_name = table->file_name; node->alloc_line_no = table->line_no; node->free_file_name = NULL; node->free_line_no = 0; node->link = pnode[0]; pnode[0] = node; table->nodes++; table->alloc_total += size; table->alloc_current += size; if ( table->alloc_current > table->alloc_max ) table->alloc_max = table->alloc_current; if ( table->nodes * 3 < table->size || table->size * 3 < table->nodes ) ft_mem_table_resize( table ); } } static void ft_mem_table_remove( FT_MemTable table, FT_Byte* address ) { if ( table ) { FT_MemNode *pnode, node; pnode = ft_mem_table_get_nodep( table, address ); node = *pnode; if ( node ) { if ( node->size < 0 ) ft_mem_debug_panic( "freeing memory block at %p more than once at (%s:%ld)\n" "block allocated at (%s:%ld) and released at (%s:%ld)", address, FT_FILENAME( table->file_name ), table->line_no, FT_FILENAME( node->alloc_file_name ), node->alloc_line_no, FT_FILENAME( node->free_file_name ), node->free_line_no ); /* we simply invert the node's size to indicate that the node */ /* was freed. We also change its contents. */ FT_MEM_SET( address, 0xF3, node->size ); table->alloc_current -= node->size; node->size = -node->size; node->free_file_name = table->file_name; node->free_line_no = table->line_no; } else ft_mem_debug_panic( "trying to free unknown block at %p in (%s:%ld)\n", address, FT_FILENAME( table->file_name ), table->line_no ); } } extern FT_Pointer ft_mem_debug_alloc( FT_Memory memory, FT_Long size ) { FT_MemTable table = (FT_MemTable)memory->user; FT_Byte* block; if ( size <= 0 ) ft_mem_debug_panic( "negative block size allocation (%ld)", size ); block = (FT_Byte *)ft_mem_table_alloc( table, size ); if ( block ) ft_mem_table_set( table, block, (FT_ULong)size ); table->file_name = NULL; table->line_no = 0; return (FT_Pointer) block; } extern void ft_mem_debug_free( FT_Memory memory, FT_Pointer block ) { FT_MemTable table = (FT_MemTable)memory->user; if ( block == NULL ) ft_mem_debug_panic( "trying to free NULL in (%s:%ld)", FT_FILENAME( table->file_name ), table->line_no ); ft_mem_table_remove( table, (FT_Byte*)block ); /* we never really free the block */ table->file_name = NULL; table->line_no = 0; } extern FT_Pointer ft_mem_debug_realloc( FT_Memory memory, FT_Long cur_size, FT_Long new_size, FT_Pointer block ) { FT_MemTable table = (FT_MemTable)memory->user; FT_MemNode node, *pnode; FT_Pointer new_block; const char* file_name = FT_FILENAME( table->file_name ); FT_Long line_no = table->line_no; if ( block == NULL || cur_size == 0 ) ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)", file_name, line_no ); if ( new_size <= 0 ) ft_mem_debug_panic( "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)", block, cur_size, file_name, line_no ); /* check 'cur_size' value */ pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block ); node = *pnode; if ( !node ) ft_mem_debug_panic( "trying to reallocate unknown block at %p in (%s:%ld)", block, file_name, line_no ); if ( node->size <= 0 ) ft_mem_debug_panic( "trying to reallocate freed block at %p in (%s:%ld)", block, file_name, line_no ); if ( node->size != cur_size ) ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is " "%ld instead of %ld in (%s:%ld)", block, cur_size, node->size, file_name, line_no ); new_block = ft_mem_debug_alloc( memory, new_size ); if ( new_block == NULL ) return NULL; ft_memcpy( new_block, block, cur_size < new_size ? cur_size : new_size ); table->file_name = file_name; table->line_no = line_no; ft_mem_debug_free( memory, (FT_Byte*)block ); return new_block; } extern FT_Int ft_mem_debug_init( FT_Memory memory ) { FT_MemTable table; FT_Int result = 0; if ( getenv( "FT_DEBUG_MEMORY" ) ) { table = ft_mem_table_new( memory ); if ( table ) { memory->user = table; memory->alloc = ft_mem_debug_alloc; memory->realloc = ft_mem_debug_realloc; memory->free = ft_mem_debug_free; result = 1; } } return result; } extern void ft_mem_debug_done( FT_Memory memory ) { FT_MemTable table = (FT_MemTable)memory->user; if ( table ) { memory->free = table->free; memory->realloc = table->realloc; memory->alloc = table->alloc; ft_mem_table_destroy( table ); memory->user = NULL; } } FT_BASE_DEF( FT_Error ) FT_Alloc_Debug( FT_Memory memory, FT_Long size, void* *P, const char* file_name, FT_Long line_no ) { FT_MemTable table = (FT_MemTable)memory->user; if ( table ) { table->file_name = file_name; table->line_no = line_no; } return FT_Alloc( memory, size, P ); } FT_BASE_DEF( FT_Error ) FT_Realloc_Debug( FT_Memory memory, FT_Long current, FT_Long size, void* *P, const char* file_name, FT_Long line_no ) { FT_MemTable table = (FT_MemTable)memory->user; if ( table ) { table->file_name = file_name; table->line_no = line_no; } return FT_Realloc( memory, current, size, P ); } FT_BASE_DEF( void ) FT_Free_Debug( FT_Memory memory, FT_Pointer block, const char* file_name, FT_Long line_no ) { FT_MemTable table = (FT_MemTable)memory->user; if ( table ) { table->file_name = file_name; table->line_no = line_no; } FT_Free( memory, (void **)block ); } #else /* !FT_DEBUG_MEMORY */ /* ANSI C doesn't like empty source files */ const FT_Byte _debug_mem_dummy = 0; #endif /* !FT_DEBUG_MEMORY */ /* END */