| /***************************************************************************/ |
| /* */ |
| /* ftdbgmem.c */ |
| /* */ |
| /* Memory debugger (body). */ |
| /* */ |
| /* Copyright 2001-2015 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 <ft2build.h> |
| #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 |
| |
| #define KEEPALIVE /* `Keep alive' means that freed blocks aren't released |
| * to the heap. This is useful to detect double-frees |
| * or weird heap corruption, but it uses large amounts of |
| * memory, however. |
| */ |
| |
| #include FT_CONFIG_STANDARD_LIBRARY_H |
| |
| FT_BASE_DEF( const char* ) _ft_debug_file = NULL; |
| FT_BASE_DEF( long ) _ft_debug_lineno = 0; |
| |
| extern void |
| FT_DumpMemory( FT_Memory memory ); |
| |
| |
| typedef struct FT_MemSourceRec_* FT_MemSource; |
| typedef struct FT_MemNodeRec_* FT_MemNode; |
| typedef struct FT_MemTableRec_* FT_MemTable; |
| |
| |
| #define FT_MEM_VAL( addr ) ( (FT_PtrDist)(FT_Pointer)( addr ) ) |
| |
| /* |
| * This structure holds statistics for a single allocation/release |
| * site. This is useful to know where memory operations happen the |
| * most. |
| */ |
| typedef struct FT_MemSourceRec_ |
| { |
| const char* file_name; |
| long line_no; |
| |
| FT_Long cur_blocks; /* current number of allocated blocks */ |
| FT_Long max_blocks; /* max. number of allocated blocks */ |
| FT_Long all_blocks; /* total number of blocks allocated */ |
| |
| FT_Long cur_size; /* current cumulative allocated size */ |
| FT_Long max_size; /* maximum cumulative allocated size */ |
| FT_Long all_size; /* total cumulative allocated size */ |
| |
| FT_Long cur_max; /* current maximum allocated size */ |
| |
| FT_UInt32 hash; |
| FT_MemSource link; |
| |
| } FT_MemSourceRec; |
| |
| |
| /* |
| * We don't need a resizable array for the memory sources because |
| * their number is pretty limited within FreeType. |
| */ |
| #define FT_MEM_SOURCE_BUCKETS 128 |
| |
| /* |
| * This structure holds information related to a single allocated |
| * memory block. If KEEPALIVE is defined, blocks that are freed by |
| * FreeType are never released to the system. Instead, their `size' |
| * field is set to `-size'. This is mainly useful to detect double |
| * frees, at the price of a large memory footprint during execution. |
| */ |
| typedef struct FT_MemNodeRec_ |
| { |
| FT_Byte* address; |
| FT_Long size; /* < 0 if the block was freed */ |
| |
| FT_MemSource source; |
| |
| #ifdef KEEPALIVE |
| const char* free_file_name; |
| FT_Long free_line_no; |
| #endif |
| |
| FT_MemNode link; |
| |
| } FT_MemNodeRec; |
| |
| |
| /* |
| * The global structure, containing compound statistics and all hash |
| * tables. |
| */ |
| typedef struct FT_MemTableRec_ |
| { |
| FT_Long size; |
| FT_Long nodes; |
| FT_MemNode* buckets; |
| |
| FT_Long alloc_total; |
| FT_Long alloc_current; |
| FT_Long alloc_max; |
| FT_Long alloc_count; |
| |
| FT_Bool bound_total; |
| FT_Long alloc_total_max; |
| |
| FT_Bool bound_count; |
| FT_Long alloc_count_max; |
| |
| FT_MemSource sources[FT_MEM_SOURCE_BUCKETS]; |
| |
| FT_Bool keep_alive; |
| |
| 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" ) |
| |
| |
| /* |
| * Prime numbers are ugly to handle. It would be better to implement |
| * L-Hashing, which is 10% faster and doesn't require divisions. |
| */ |
| static const FT_Int 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, |
| }; |
| |
| |
| static FT_Long |
| ft_mem_closest_prime( FT_Long num ) |
| { |
| size_t 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 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_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_Long new_size; |
| |
| |
| new_size = ft_mem_closest_prime( table->nodes ); |
| if ( new_size != table->size ) |
| { |
| FT_MemNode* new_buckets; |
| FT_Long i; |
| |
| |
| new_buckets = (FT_MemNode *) |
| ft_mem_table_alloc( |
| table, |
| new_size * (FT_Long)sizeof ( FT_MemNode ) ); |
| if ( new_buckets == NULL ) |
| return; |
| |
| FT_ARRAY_ZERO( new_buckets, new_size ); |
| |
| for ( i = 0; i < table->size; i++ ) |
| { |
| FT_MemNode node, next, *pnode; |
| FT_PtrDist hash; |
| |
| |
| node = table->buckets[i]; |
| while ( node ) |
| { |
| next = node->link; |
| hash = FT_MEM_VAL( node->address ) % (FT_PtrDist)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_ZERO( 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 * (FT_Long)sizeof ( FT_MemNode ) ); |
| if ( table->buckets ) |
| FT_ARRAY_ZERO( table->buckets, table->size ); |
| else |
| { |
| memory->free( memory, table ); |
| table = NULL; |
| } |
| |
| Exit: |
| return table; |
| } |
| |
| |
| static void |
| ft_mem_table_destroy( FT_MemTable table ) |
| { |
| FT_Long i; |
| FT_Long leak_count = 0; |
| FT_Long leaks = 0; |
| |
| |
| FT_DumpMemory( table->memory ); |
| |
| /* remove all blocks from the table, revealing leaked ones */ |
| for ( i = 0; i < table->size; i++ ) |
| { |
| FT_MemNode *pnode = table->buckets + i, next, node = *pnode; |
| |
| |
| while ( node ) |
| { |
| next = node->link; |
| node->link = NULL; |
| |
| if ( node->size > 0 ) |
| { |
| printf( |
| "leaked memory block at address %p, size %8ld in (%s:%ld)\n", |
| node->address, node->size, |
| FT_FILENAME( node->source->file_name ), |
| node->source->line_no ); |
| |
| leak_count++; |
| leaks += node->size; |
| |
| ft_mem_table_free( table, node->address ); |
| } |
| |
| node->address = NULL; |
| node->size = 0; |
| |
| ft_mem_table_free( table, node ); |
| node = next; |
| } |
| table->buckets[i] = NULL; |
| } |
| |
| ft_mem_table_free( table, table->buckets ); |
| table->buckets = NULL; |
| |
| table->size = 0; |
| table->nodes = 0; |
| |
| /* remove all sources */ |
| for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ ) |
| { |
| FT_MemSource source, next; |
| |
| |
| for ( source = table->sources[i]; source != NULL; source = next ) |
| { |
| next = source->link; |
| ft_mem_table_free( table, source ); |
| } |
| |
| table->sources[i] = NULL; |
| } |
| |
| printf( "FreeType: total memory allocations = %ld\n", |
| table->alloc_total ); |
| printf( "FreeType: maximum memory footprint = %ld\n", |
| table->alloc_max ); |
| |
| ft_mem_table_free( table, 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_PtrDist hash; |
| FT_MemNode *pnode, node; |
| |
| |
| hash = FT_MEM_VAL( address ); |
| pnode = table->buckets + ( hash % (FT_PtrDist)table->size ); |
| |
| for (;;) |
| { |
| node = pnode[0]; |
| if ( !node ) |
| break; |
| |
| if ( node->address == address ) |
| break; |
| |
| pnode = &node->link; |
| } |
| return pnode; |
| } |
| |
| |
| static FT_MemSource |
| ft_mem_table_get_source( FT_MemTable table ) |
| { |
| FT_UInt32 hash; |
| FT_MemSource node, *pnode; |
| |
| |
| /* cast to FT_PtrDist first since void* can be larger */ |
| /* than FT_UInt32 and GCC 4.1.1 emits a warning */ |
| hash = (FT_UInt32)(FT_PtrDist)(void*)_ft_debug_file + |
| (FT_UInt32)( 5 * _ft_debug_lineno ); |
| pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS]; |
| |
| for ( ;; ) |
| { |
| node = *pnode; |
| if ( node == NULL ) |
| break; |
| |
| if ( node->file_name == _ft_debug_file && |
| node->line_no == _ft_debug_lineno ) |
| goto Exit; |
| |
| pnode = &node->link; |
| } |
| |
| node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) ); |
| if ( node == NULL ) |
| ft_mem_debug_panic( |
| "not enough memory to perform memory debugging\n" ); |
| |
| node->file_name = _ft_debug_file; |
| node->line_no = _ft_debug_lineno; |
| |
| node->cur_blocks = 0; |
| node->max_blocks = 0; |
| node->all_blocks = 0; |
| |
| node->cur_size = 0; |
| node->max_size = 0; |
| node->all_size = 0; |
| |
| node->cur_max = 0; |
| |
| node->link = NULL; |
| node->hash = hash; |
| *pnode = node; |
| |
| Exit: |
| return node; |
| } |
| |
| |
| static void |
| ft_mem_table_set( FT_MemTable table, |
| FT_Byte* address, |
| FT_Long size, |
| FT_Long delta ) |
| { |
| FT_MemNode *pnode, node; |
| |
| |
| if ( table ) |
| { |
| FT_MemSource source; |
| |
| |
| pnode = ft_mem_table_get_nodep( table, address ); |
| node = *pnode; |
| if ( node ) |
| { |
| if ( node->size < 0 ) |
| { |
| /* This block was already freed. Our memory is now completely */ |
| /* corrupted! */ |
| /* This can only happen in keep-alive mode. */ |
| 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 at" |
| " %p, of size %ld)\n" |
| "org=%s:%d new=%s:%d\n", |
| node->address, node->size, |
| FT_FILENAME( node->source->file_name ), node->source->line_no, |
| FT_FILENAME( _ft_debug_file ), _ft_debug_lineno ); |
| } |
| } |
| |
| /* 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->source = source = ft_mem_table_get_source( table ); |
| |
| if ( delta == 0 ) |
| { |
| /* this is an allocation */ |
| source->all_blocks++; |
| source->cur_blocks++; |
| if ( source->cur_blocks > source->max_blocks ) |
| source->max_blocks = source->cur_blocks; |
| } |
| |
| if ( size > source->cur_max ) |
| source->cur_max = size; |
| |
| if ( delta != 0 ) |
| { |
| /* we are growing or shrinking a reallocated block */ |
| source->cur_size += delta; |
| table->alloc_current += delta; |
| } |
| else |
| { |
| /* we are allocating a new block */ |
| source->cur_size += size; |
| table->alloc_current += size; |
| } |
| |
| source->all_size += size; |
| |
| if ( source->cur_size > source->max_size ) |
| source->max_size = source->cur_size; |
| |
| node->free_file_name = NULL; |
| node->free_line_no = 0; |
| |
| node->link = pnode[0]; |
| |
| pnode[0] = node; |
| table->nodes++; |
| |
| table->alloc_total += 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, |
| FT_Long delta ) |
| { |
| if ( table ) |
| { |
| FT_MemNode *pnode, node; |
| |
| |
| pnode = ft_mem_table_get_nodep( table, address ); |
| node = *pnode; |
| if ( node ) |
| { |
| FT_MemSource source; |
| |
| |
| 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( _ft_debug_file ), _ft_debug_lineno, |
| FT_FILENAME( node->source->file_name ), node->source->line_no, |
| FT_FILENAME( node->free_file_name ), node->free_line_no ); |
| |
| /* scramble the node's content for additional safety */ |
| FT_MEM_SET( address, 0xF3, node->size ); |
| |
| if ( delta == 0 ) |
| { |
| source = node->source; |
| |
| source->cur_blocks--; |
| source->cur_size -= node->size; |
| |
| table->alloc_current -= node->size; |
| } |
| |
| if ( table->keep_alive ) |
| { |
| /* we simply invert the node's size to indicate that the node */ |
| /* was freed. */ |
| node->size = -node->size; |
| node->free_file_name = _ft_debug_file; |
| node->free_line_no = _ft_debug_lineno; |
| } |
| else |
| { |
| table->nodes--; |
| |
| *pnode = node->link; |
| |
| node->size = 0; |
| node->source = NULL; |
| |
| ft_mem_table_free( table, node ); |
| |
| if ( table->nodes * 3 < table->size || |
| table->size * 3 < table->nodes ) |
| ft_mem_table_resize( table ); |
| } |
| } |
| else |
| ft_mem_debug_panic( |
| "trying to free unknown block at %p in (%s:%ld)\n", |
| address, |
| FT_FILENAME( _ft_debug_file ), _ft_debug_lineno ); |
| } |
| } |
| |
| |
| static 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 ); |
| |
| /* return NULL if the maximum number of allocations was reached */ |
| if ( table->bound_count && |
| table->alloc_count >= table->alloc_count_max ) |
| return NULL; |
| |
| /* return NULL if this allocation would overflow the maximum heap size */ |
| if ( table->bound_total && |
| table->alloc_total_max - table->alloc_current > size ) |
| return NULL; |
| |
| block = (FT_Byte *)ft_mem_table_alloc( table, size ); |
| if ( block ) |
| { |
| ft_mem_table_set( table, block, size, 0 ); |
| |
| table->alloc_count++; |
| } |
| |
| _ft_debug_file = "<unknown>"; |
| _ft_debug_lineno = 0; |
| |
| return (FT_Pointer)block; |
| } |
| |
| |
| static 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( _ft_debug_file ), |
| _ft_debug_lineno ); |
| |
| ft_mem_table_remove( table, (FT_Byte*)block, 0 ); |
| |
| if ( !table->keep_alive ) |
| ft_mem_table_free( table, block ); |
| |
| table->alloc_count--; |
| |
| _ft_debug_file = "<unknown>"; |
| _ft_debug_lineno = 0; |
| } |
| |
| |
| static 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; |
| FT_Long delta; |
| |
| const char* file_name = FT_FILENAME( _ft_debug_file ); |
| FT_Long line_no = _ft_debug_lineno; |
| |
| |
| /* unlikely, but possible */ |
| if ( new_size == cur_size ) |
| return block; |
| |
| /* the following is valid according to ANSI C */ |
| #if 0 |
| if ( block == NULL || cur_size == 0 ) |
| ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)", |
| file_name, line_no ); |
| #endif |
| |
| /* while the following is allowed in ANSI C also, we abort since */ |
| /* such case should be handled by FreeType. */ |
| 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 ); |
| |
| /* return NULL if the maximum number of allocations was reached */ |
| if ( table->bound_count && |
| table->alloc_count >= table->alloc_count_max ) |
| return NULL; |
| |
| delta = new_size - cur_size; |
| |
| /* return NULL if this allocation would overflow the maximum heap size */ |
| if ( delta > 0 && |
| table->bound_total && |
| table->alloc_current + delta > table->alloc_total_max ) |
| return NULL; |
| |
| new_block = (FT_Pointer)ft_mem_table_alloc( table, new_size ); |
| if ( new_block == NULL ) |
| return NULL; |
| |
| ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta ); |
| |
| ft_memcpy( new_block, block, cur_size < new_size ? (size_t)cur_size |
| : (size_t)new_size ); |
| |
| ft_mem_table_remove( table, (FT_Byte*)block, delta ); |
| |
| _ft_debug_file = "<unknown>"; |
| _ft_debug_lineno = 0; |
| |
| if ( !table->keep_alive ) |
| ft_mem_table_free( table, block ); |
| |
| return new_block; |
| } |
| |
| |
| extern FT_Int |
| ft_mem_debug_init( FT_Memory memory ) |
| { |
| FT_MemTable table; |
| FT_Int result = 0; |
| |
| |
| if ( getenv( "FT2_DEBUG_MEMORY" ) ) |
| { |
| table = ft_mem_table_new( memory ); |
| if ( table ) |
| { |
| const char* p; |
| |
| |
| memory->user = table; |
| memory->alloc = ft_mem_debug_alloc; |
| memory->realloc = ft_mem_debug_realloc; |
| memory->free = ft_mem_debug_free; |
| |
| p = getenv( "FT2_ALLOC_TOTAL_MAX" ); |
| if ( p != NULL ) |
| { |
| FT_Long total_max = ft_atol( p ); |
| |
| |
| if ( total_max > 0 ) |
| { |
| table->bound_total = 1; |
| table->alloc_total_max = total_max; |
| } |
| } |
| |
| p = getenv( "FT2_ALLOC_COUNT_MAX" ); |
| if ( p != NULL ) |
| { |
| FT_Long total_count = ft_atol( p ); |
| |
| |
| if ( total_count > 0 ) |
| { |
| table->bound_count = 1; |
| table->alloc_count_max = total_count; |
| } |
| } |
| |
| p = getenv( "FT2_KEEP_ALIVE" ); |
| if ( p != NULL ) |
| { |
| FT_Long keep_alive = ft_atol( p ); |
| |
| |
| if ( keep_alive > 0 ) |
| table->keep_alive = 1; |
| } |
| |
| 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; |
| } |
| } |
| |
| |
| static int |
| ft_mem_source_compare( const void* p1, |
| const void* p2 ) |
| { |
| FT_MemSource s1 = *(FT_MemSource*)p1; |
| FT_MemSource s2 = *(FT_MemSource*)p2; |
| |
| |
| if ( s2->max_size > s1->max_size ) |
| return 1; |
| else if ( s2->max_size < s1->max_size ) |
| return -1; |
| else |
| return 0; |
| } |
| |
| |
| extern void |
| FT_DumpMemory( FT_Memory memory ) |
| { |
| FT_MemTable table = (FT_MemTable)memory->user; |
| |
| |
| if ( table ) |
| { |
| FT_MemSource* bucket = table->sources; |
| FT_MemSource* limit = bucket + FT_MEM_SOURCE_BUCKETS; |
| FT_MemSource* sources; |
| FT_Int nn, count; |
| const char* fmt; |
| |
| |
| count = 0; |
| for ( ; bucket < limit; bucket++ ) |
| { |
| FT_MemSource source = *bucket; |
| |
| |
| for ( ; source; source = source->link ) |
| count++; |
| } |
| |
| sources = (FT_MemSource*) |
| ft_mem_table_alloc( |
| table, count * (FT_Long)sizeof ( *sources ) ); |
| |
| count = 0; |
| for ( bucket = table->sources; bucket < limit; bucket++ ) |
| { |
| FT_MemSource source = *bucket; |
| |
| |
| for ( ; source; source = source->link ) |
| sources[count++] = source; |
| } |
| |
| ft_qsort( sources, |
| (size_t)count, |
| sizeof ( *sources ), |
| ft_mem_source_compare ); |
| |
| printf( "FreeType Memory Dump: " |
| "current=%ld max=%ld total=%ld count=%ld\n", |
| table->alloc_current, table->alloc_max, |
| table->alloc_total, table->alloc_count ); |
| printf( " block block sizes sizes sizes source\n" ); |
| printf( " count high sum highsum max location\n" ); |
| printf( "-------------------------------------------------\n" ); |
| |
| fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n"; |
| |
| for ( nn = 0; nn < count; nn++ ) |
| { |
| FT_MemSource source = sources[nn]; |
| |
| |
| printf( fmt, |
| source->cur_blocks, source->max_blocks, |
| source->cur_size, source->max_size, source->cur_max, |
| FT_FILENAME( source->file_name ), |
| source->line_no ); |
| } |
| printf( "------------------------------------------------\n" ); |
| |
| ft_mem_table_free( table, sources ); |
| } |
| } |
| |
| #else /* !FT_DEBUG_MEMORY */ |
| |
| /* ANSI C doesn't like empty source files */ |
| typedef int _debug_mem_dummy; |
| |
| #endif /* !FT_DEBUG_MEMORY */ |
| |
| |
| /* END */ |