-/***************************************************************************/\r
-/* */\r
-/* ftdbgmem.c */\r
-/* */\r
-/* Memory debugger (body). */\r
-/* */\r
-/* Copyright 2001, 2002, 2003, 2004, 2005, 2006 by */\r
-/* David Turner, Robert Wilhelm, and Werner Lemberg. */\r
-/* */\r
-/* This file is part of the FreeType project, and may only be used, */\r
-/* modified, and distributed under the terms of the FreeType project */\r
-/* license, LICENSE.TXT. By continuing to use, modify, or distribute */\r
-/* this file you indicate that you have read the license and */\r
-/* understand and accept it fully. */\r
-/* */\r
-/***************************************************************************/\r
-\r
-\r
-#include <ft2build.h>\r
-#include FT_CONFIG_CONFIG_H\r
-#include FT_INTERNAL_DEBUG_H\r
-#include FT_INTERNAL_MEMORY_H\r
-#include FT_SYSTEM_H\r
-#include FT_ERRORS_H\r
-#include FT_TYPES_H\r
-\r
-\r
-#ifdef FT_DEBUG_MEMORY\r
-\r
-#define KEEPALIVE /* `Keep alive' means that freed blocks aren't released\r
- * to the heap. This is useful to detect double-frees\r
- * or weird heap corruption, but it uses large amounts of\r
- * memory, however.\r
- */\r
-\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-\r
- FT_BASE_DEF( const char* ) _ft_debug_file = 0;\r
- FT_BASE_DEF( long ) _ft_debug_lineno = 0;\r
-\r
- extern void\r
- FT_DumpMemory( FT_Memory memory );\r
-\r
-\r
- typedef struct FT_MemSourceRec_* FT_MemSource;\r
- typedef struct FT_MemNodeRec_* FT_MemNode;\r
- typedef struct FT_MemTableRec_* FT_MemTable;\r
-\r
-\r
-#define FT_MEM_VAL( addr ) ((FT_ULong)(FT_Pointer)( addr ))\r
-\r
- /*\r
- * This structure holds statistics for a single allocation/release\r
- * site. This is useful to know where memory operations happen the\r
- * most.\r
- */\r
- typedef struct FT_MemSourceRec_\r
- {\r
- const char* file_name;\r
- long line_no;\r
-\r
- FT_Long cur_blocks; /* current number of allocated blocks */\r
- FT_Long max_blocks; /* max. number of allocated blocks */\r
- FT_Long all_blocks; /* total number of blocks allocated */\r
-\r
- FT_Long cur_size; /* current cumulative allocated size */\r
- FT_Long max_size; /* maximum cumulative allocated size */\r
- FT_Long all_size; /* total cumulative allocated size */\r
-\r
- FT_Long cur_max; /* current maximum allocated size */\r
-\r
- FT_UInt32 hash;\r
- FT_MemSource link;\r
-\r
- } FT_MemSourceRec;\r
-\r
-\r
- /*\r
- * We don't need a resizable array for the memory sources, because\r
- * their number is pretty limited within FreeType.\r
- */\r
-#define FT_MEM_SOURCE_BUCKETS 128\r
-\r
- /*\r
- * This structure holds information related to a single allocated\r
- * memory block. If KEEPALIVE is defined, blocks that are freed by\r
- * FreeType are never released to the system. Instead, their `size'\r
- * field is set to -size. This is mainly useful to detect double frees,\r
- * at the price of large memory footprint during execution.\r
- */\r
- typedef struct FT_MemNodeRec_\r
- {\r
- FT_Byte* address;\r
- FT_Long size; /* < 0 if the block was freed */\r
-\r
- FT_MemSource source;\r
-\r
-#ifdef KEEPALIVE\r
- const char* free_file_name;\r
- FT_Long free_line_no;\r
-#endif\r
-\r
- FT_MemNode link;\r
-\r
- } FT_MemNodeRec;\r
-\r
-\r
- /*\r
- * The global structure, containing compound statistics and all hash\r
- * tables.\r
- */\r
- typedef struct FT_MemTableRec_\r
- {\r
- FT_ULong size;\r
- FT_ULong nodes;\r
- FT_MemNode* buckets;\r
-\r
- FT_ULong alloc_total;\r
- FT_ULong alloc_current;\r
- FT_ULong alloc_max;\r
- FT_ULong alloc_count;\r
-\r
- FT_Bool bound_total;\r
- FT_ULong alloc_total_max;\r
-\r
- FT_Bool bound_count;\r
- FT_ULong alloc_count_max;\r
-\r
- FT_MemSource sources[FT_MEM_SOURCE_BUCKETS];\r
-\r
- FT_Bool keep_alive;\r
-\r
- FT_Memory memory;\r
- FT_Pointer memory_user;\r
- FT_Alloc_Func alloc;\r
- FT_Free_Func free;\r
- FT_Realloc_Func realloc;\r
-\r
- } FT_MemTableRec;\r
-\r
-\r
-#define FT_MEM_SIZE_MIN 7\r
-#define FT_MEM_SIZE_MAX 13845163\r
-\r
-#define FT_FILENAME( x ) ((x) ? (x) : "unknown file")\r
-\r
-\r
- /*\r
- * Prime numbers are ugly to handle. It would be better to implement\r
- * L-Hashing, which is 10% faster and doesn't require divisions.\r
- */\r
- static const FT_UInt ft_mem_primes[] =\r
- {\r
- 7,\r
- 11,\r
- 19,\r
- 37,\r
- 73,\r
- 109,\r
- 163,\r
- 251,\r
- 367,\r
- 557,\r
- 823,\r
- 1237,\r
- 1861,\r
- 2777,\r
- 4177,\r
- 6247,\r
- 9371,\r
- 14057,\r
- 21089,\r
- 31627,\r
- 47431,\r
- 71143,\r
- 106721,\r
- 160073,\r
- 240101,\r
- 360163,\r
- 540217,\r
- 810343,\r
- 1215497,\r
- 1823231,\r
- 2734867,\r
- 4102283,\r
- 6153409,\r
- 9230113,\r
- 13845163,\r
- };\r
-\r
-\r
- static FT_ULong\r
- ft_mem_closest_prime( FT_ULong num )\r
- {\r
- FT_UInt i;\r
-\r
-\r
- for ( i = 0;\r
- i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ )\r
- if ( ft_mem_primes[i] > num )\r
- return ft_mem_primes[i];\r
-\r
- return FT_MEM_SIZE_MAX;\r
- }\r
-\r
-\r
- extern void\r
- ft_mem_debug_panic( const char* fmt,\r
- ... )\r
- {\r
- va_list ap;\r
-\r
-\r
- printf( "FreeType.Debug: " );\r
-\r
- va_start( ap, fmt );\r
- vprintf( fmt, ap );\r
- va_end( ap );\r
-\r
- printf( "\n" );\r
- exit( EXIT_FAILURE );\r
- }\r
-\r
-\r
- static FT_Pointer\r
- ft_mem_table_alloc( FT_MemTable table,\r
- FT_Long size )\r
- {\r
- FT_Memory memory = table->memory;\r
- FT_Pointer block;\r
-\r
-\r
- memory->user = table->memory_user;\r
- block = table->alloc( memory, size );\r
- memory->user = table;\r
-\r
- return block;\r
- }\r
-\r
-\r
- static void\r
- ft_mem_table_free( FT_MemTable table,\r
- FT_Pointer block )\r
- {\r
- FT_Memory memory = table->memory;\r
-\r
-\r
- memory->user = table->memory_user;\r
- table->free( memory, block );\r
- memory->user = table;\r
- }\r
-\r
-\r
- static void\r
- ft_mem_table_resize( FT_MemTable table )\r
- {\r
- FT_ULong new_size;\r
-\r
-\r
- new_size = ft_mem_closest_prime( table->nodes );\r
- if ( new_size != table->size )\r
- {\r
- FT_MemNode* new_buckets;\r
- FT_ULong i;\r
-\r
-\r
- new_buckets = (FT_MemNode *)\r
- ft_mem_table_alloc( table,\r
- new_size * sizeof ( FT_MemNode ) );\r
- if ( new_buckets == NULL )\r
- return;\r
-\r
- FT_ARRAY_ZERO( new_buckets, new_size );\r
-\r
- for ( i = 0; i < table->size; i++ )\r
- {\r
- FT_MemNode node, next, *pnode;\r
- FT_ULong hash;\r
-\r
-\r
- node = table->buckets[i];\r
- while ( node )\r
- {\r
- next = node->link;\r
- hash = FT_MEM_VAL( node->address ) % new_size;\r
- pnode = new_buckets + hash;\r
-\r
- node->link = pnode[0];\r
- pnode[0] = node;\r
-\r
- node = next;\r
- }\r
- }\r
-\r
- if ( table->buckets )\r
- ft_mem_table_free( table, table->buckets );\r
-\r
- table->buckets = new_buckets;\r
- table->size = new_size;\r
- }\r
- }\r
-\r
-\r
- static FT_MemTable\r
- ft_mem_table_new( FT_Memory memory )\r
- {\r
- FT_MemTable table;\r
-\r
-\r
- table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) );\r
- if ( table == NULL )\r
- goto Exit;\r
-\r
- FT_ZERO( table );\r
-\r
- table->size = FT_MEM_SIZE_MIN;\r
- table->nodes = 0;\r
-\r
- table->memory = memory;\r
-\r
- table->memory_user = memory->user;\r
-\r
- table->alloc = memory->alloc;\r
- table->realloc = memory->realloc;\r
- table->free = memory->free;\r
-\r
- table->buckets = (FT_MemNode *)\r
- memory->alloc( memory,\r
- table->size * sizeof ( FT_MemNode ) );\r
- if ( table->buckets )\r
- FT_ARRAY_ZERO( table->buckets, table->size );\r
- else\r
- {\r
- memory->free( memory, table );\r
- table = NULL;\r
- }\r
-\r
- Exit:\r
- return table;\r
- }\r
-\r
-\r
- static void\r
- ft_mem_table_destroy( FT_MemTable table )\r
- {\r
- FT_ULong i;\r
-\r
-\r
- FT_DumpMemory( table->memory );\r
-\r
- if ( table )\r
- {\r
- FT_Long leak_count = 0;\r
- FT_ULong leaks = 0;\r
-\r
-\r
- /* remove all blocks from the table, revealing leaked ones */\r
- for ( i = 0; i < table->size; i++ )\r
- {\r
- FT_MemNode *pnode = table->buckets + i, next, node = *pnode;\r
-\r
-\r
- while ( node )\r
- {\r
- next = node->link;\r
- node->link = 0;\r
-\r
- if ( node->size > 0 )\r
- {\r
- printf(\r
- "leaked memory block at address %p, size %8ld in (%s:%ld)\n",\r
- node->address, node->size,\r
- FT_FILENAME( node->source->file_name ),\r
- node->source->line_no );\r
-\r
- leak_count++;\r
- leaks += node->size;\r
-\r
- ft_mem_table_free( table, node->address );\r
- }\r
-\r
- node->address = NULL;\r
- node->size = 0;\r
-\r
- ft_mem_table_free( table, node );\r
- node = next;\r
- }\r
- table->buckets[i] = 0;\r
- }\r
-\r
- ft_mem_table_free( table, table->buckets );\r
- table->buckets = NULL;\r
-\r
- table->size = 0;\r
- table->nodes = 0;\r
-\r
- /* remove all sources */\r
- for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ )\r
- {\r
- FT_MemSource source, next;\r
-\r
-\r
- for ( source = table->sources[i]; source != NULL; source = next )\r
- {\r
- next = source->link;\r
- ft_mem_table_free( table, source );\r
- }\r
-\r
- table->sources[i] = NULL;\r
- }\r
-\r
- printf(\r
- "FreeType: total memory allocations = %ld\n", table->alloc_total );\r
- printf(\r
- "FreeType: maximum memory footprint = %ld\n", table->alloc_max );\r
-\r
- ft_mem_table_free( table, table );\r
-\r
- if ( leak_count > 0 )\r
- ft_mem_debug_panic(\r
- "FreeType: %ld bytes of memory leaked in %ld blocks\n",\r
- leaks, leak_count );\r
-\r
- printf( "FreeType: No memory leaks detected!\n" );\r
- }\r
- }\r
-\r
-\r
- static FT_MemNode*\r
- ft_mem_table_get_nodep( FT_MemTable table,\r
- FT_Byte* address )\r
- {\r
- FT_ULong hash;\r
- FT_MemNode *pnode, node;\r
-\r
-\r
- hash = FT_MEM_VAL( address );\r
- pnode = table->buckets + ( hash % table->size );\r
-\r
- for (;;)\r
- {\r
- node = pnode[0];\r
- if ( !node )\r
- break;\r
-\r
- if ( node->address == address )\r
- break;\r
-\r
- pnode = &node->link;\r
- }\r
- return pnode;\r
- }\r
-\r
-\r
- static FT_MemSource\r
- ft_mem_table_get_source( FT_MemTable table )\r
- {\r
- FT_UInt32 hash;\r
- FT_MemSource node, *pnode;\r
-\r
-\r
- /* cast to FT_PtrDist first since void* can be larger */\r
- /* than FT_UInt32 and GCC 4.1.1 emits a warning */\r
- hash = (FT_UInt32)(FT_PtrDist)(void*)_ft_debug_file +\r
- (FT_UInt32)( 5 * _ft_debug_lineno );\r
- pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS];\r
-\r
- for ( ;; )\r
- {\r
- node = *pnode;\r
- if ( node == NULL )\r
- break;\r
-\r
- if ( node->file_name == _ft_debug_file &&\r
- node->line_no == _ft_debug_lineno )\r
- goto Exit;\r
-\r
- pnode = &node->link;\r
- }\r
-\r
- node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) );\r
- if ( node == NULL )\r
- ft_mem_debug_panic(\r
- "not enough memory to perform memory debugging\n" );\r
-\r
- node->file_name = _ft_debug_file;\r
- node->line_no = _ft_debug_lineno;\r
-\r
- node->cur_blocks = 0;\r
- node->max_blocks = 0;\r
- node->all_blocks = 0;\r
-\r
- node->cur_size = 0;\r
- node->max_size = 0;\r
- node->all_size = 0;\r
-\r
- node->cur_max = 0;\r
-\r
- node->link = NULL;\r
- node->hash = hash;\r
- *pnode = node;\r
-\r
- Exit:\r
- return node;\r
- }\r
-\r
-\r
- static void\r
- ft_mem_table_set( FT_MemTable table,\r
- FT_Byte* address,\r
- FT_ULong size,\r
- FT_Long delta )\r
- {\r
- FT_MemNode *pnode, node;\r
-\r
-\r
- if ( table )\r
- {\r
- FT_MemSource source;\r
-\r
-\r
- pnode = ft_mem_table_get_nodep( table, address );\r
- node = *pnode;\r
- if ( node )\r
- {\r
- if ( node->size < 0 )\r
- {\r
- /* This block was already freed. Our memory is now completely */\r
- /* corrupted! */\r
- /* This can only happen in keep-alive mode. */\r
- ft_mem_debug_panic(\r
- "memory heap corrupted (allocating freed block)" );\r
- }\r
- else\r
- {\r
- /* This block was already allocated. This means that our memory */\r
- /* is also corrupted! */\r
- ft_mem_debug_panic(\r
- "memory heap corrupted (re-allocating allocated block at"\r
- " %p, of size %ld)\n"\r
- "org=%s:%d new=%s:%d\n",\r
- node->address, node->size,\r
- FT_FILENAME( node->source->file_name ), node->source->line_no,\r
- FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );\r
- }\r
- }\r
-\r
- /* we need to create a new node in this table */\r
- node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) );\r
- if ( node == NULL )\r
- ft_mem_debug_panic( "not enough memory to run memory tests" );\r
-\r
- node->address = address;\r
- node->size = size;\r
- node->source = source = ft_mem_table_get_source( table );\r
-\r
- if ( delta == 0 )\r
- {\r
- /* this is an allocation */\r
- source->all_blocks++;\r
- source->cur_blocks++;\r
- if ( source->cur_blocks > source->max_blocks )\r
- source->max_blocks = source->cur_blocks;\r
- }\r
-\r
- if ( size > (FT_ULong)source->cur_max )\r
- source->cur_max = size;\r
-\r
- if ( delta != 0 )\r
- {\r
- /* we are growing or shrinking a reallocated block */\r
- source->cur_size += delta;\r
- table->alloc_current += delta;\r
- }\r
- else\r
- {\r
- /* we are allocating a new block */\r
- source->cur_size += size;\r
- table->alloc_current += size;\r
- }\r
-\r
- source->all_size += size;\r
-\r
- if ( source->cur_size > source->max_size )\r
- source->max_size = source->cur_size;\r
-\r
- node->free_file_name = NULL;\r
- node->free_line_no = 0;\r
-\r
- node->link = pnode[0];\r
-\r
- pnode[0] = node;\r
- table->nodes++;\r
-\r
- table->alloc_total += size;\r
-\r
- if ( table->alloc_current > table->alloc_max )\r
- table->alloc_max = table->alloc_current;\r
-\r
- if ( table->nodes * 3 < table->size ||\r
- table->size * 3 < table->nodes )\r
- ft_mem_table_resize( table );\r
- }\r
- }\r
-\r
-\r
- static void\r
- ft_mem_table_remove( FT_MemTable table,\r
- FT_Byte* address,\r
- FT_Long delta )\r
- {\r
- if ( table )\r
- {\r
- FT_MemNode *pnode, node;\r
-\r
-\r
- pnode = ft_mem_table_get_nodep( table, address );\r
- node = *pnode;\r
- if ( node )\r
- {\r
- FT_MemSource source;\r
-\r
-\r
- if ( node->size < 0 )\r
- ft_mem_debug_panic(\r
- "freeing memory block at %p more than once at (%s:%ld)\n"\r
- "block allocated at (%s:%ld) and released at (%s:%ld)",\r
- address,\r
- FT_FILENAME( _ft_debug_file ), _ft_debug_lineno,\r
- FT_FILENAME( node->source->file_name ), node->source->line_no,\r
- FT_FILENAME( node->free_file_name ), node->free_line_no );\r
-\r
- /* scramble the node's content for additional safety */\r
- FT_MEM_SET( address, 0xF3, node->size );\r
-\r
- if ( delta == 0 )\r
- {\r
- source = node->source;\r
-\r
- source->cur_blocks--;\r
- source->cur_size -= node->size;\r
-\r
- table->alloc_current -= node->size;\r
- }\r
-\r
- if ( table->keep_alive )\r
- {\r
- /* we simply invert the node's size to indicate that the node */\r
- /* was freed. */\r
- node->size = -node->size;\r
- node->free_file_name = _ft_debug_file;\r
- node->free_line_no = _ft_debug_lineno;\r
- }\r
- else\r
- {\r
- table->nodes--;\r
-\r
- *pnode = node->link;\r
-\r
- node->size = 0;\r
- node->source = NULL;\r
-\r
- ft_mem_table_free( table, node );\r
-\r
- if ( table->nodes * 3 < table->size ||\r
- table->size * 3 < table->nodes )\r
- ft_mem_table_resize( table );\r
- }\r
- }\r
- else\r
- ft_mem_debug_panic(\r
- "trying to free unknown block at %p in (%s:%ld)\n",\r
- address,\r
- FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );\r
- }\r
- }\r
-\r
-\r
- extern FT_Pointer\r
- ft_mem_debug_alloc( FT_Memory memory,\r
- FT_Long size )\r
- {\r
- FT_MemTable table = (FT_MemTable)memory->user;\r
- FT_Byte* block;\r
-\r
-\r
- if ( size <= 0 )\r
- ft_mem_debug_panic( "negative block size allocation (%ld)", size );\r
-\r
- /* return NULL if the maximum number of allocations was reached */\r
- if ( table->bound_count &&\r
- table->alloc_count >= table->alloc_count_max )\r
- return NULL;\r
-\r
- /* return NULL if this allocation would overflow the maximum heap size */\r
- if ( table->bound_total &&\r
- table->alloc_total_max - table->alloc_current > (FT_ULong)size )\r
- return NULL;\r
-\r
- block = (FT_Byte *)ft_mem_table_alloc( table, size );\r
- if ( block )\r
- {\r
- ft_mem_table_set( table, block, (FT_ULong)size, 0 );\r
-\r
- table->alloc_count++;\r
- }\r
-\r
- _ft_debug_file = "<unknown>";\r
- _ft_debug_lineno = 0;\r
-\r
- return (FT_Pointer)block;\r
- }\r
-\r
-\r
- extern void\r
- ft_mem_debug_free( FT_Memory memory,\r
- FT_Pointer block )\r
- {\r
- FT_MemTable table = (FT_MemTable)memory->user;\r
-\r
-\r
- if ( block == NULL )\r
- ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",\r
- FT_FILENAME( _ft_debug_file ),\r
- _ft_debug_lineno );\r
-\r
- ft_mem_table_remove( table, (FT_Byte*)block, 0 );\r
-\r
- if ( !table->keep_alive )\r
- ft_mem_table_free( table, block );\r
-\r
- table->alloc_count--;\r
-\r
- _ft_debug_file = "<unknown>";\r
- _ft_debug_lineno = 0;\r
- }\r
-\r
-\r
- extern FT_Pointer\r
- ft_mem_debug_realloc( FT_Memory memory,\r
- FT_Long cur_size,\r
- FT_Long new_size,\r
- FT_Pointer block )\r
- {\r
- FT_MemTable table = (FT_MemTable)memory->user;\r
- FT_MemNode node, *pnode;\r
- FT_Pointer new_block;\r
- FT_Long delta;\r
-\r
- const char* file_name = FT_FILENAME( _ft_debug_file );\r
- FT_Long line_no = _ft_debug_lineno;\r
-\r
-\r
- /* unlikely, but possible */\r
- if ( new_size == cur_size )\r
- return block;\r
-\r
- /* the following is valid according to ANSI C */\r
-#if 0\r
- if ( block == NULL || cur_size == 0 )\r
- ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",\r
- file_name, line_no );\r
-#endif\r
-\r
- /* while the following is allowed in ANSI C also, we abort since */\r
- /* such case should be handled by FreeType. */\r
- if ( new_size <= 0 )\r
- ft_mem_debug_panic(\r
- "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",\r
- block, cur_size, file_name, line_no );\r
-\r
- /* check `cur_size' value */\r
- pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );\r
- node = *pnode;\r
- if ( !node )\r
- ft_mem_debug_panic(\r
- "trying to reallocate unknown block at %p in (%s:%ld)",\r
- block, file_name, line_no );\r
-\r
- if ( node->size <= 0 )\r
- ft_mem_debug_panic(\r
- "trying to reallocate freed block at %p in (%s:%ld)",\r
- block, file_name, line_no );\r
-\r
- if ( node->size != cur_size )\r
- ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is "\r
- "%ld instead of %ld in (%s:%ld)",\r
- block, cur_size, node->size, file_name, line_no );\r
-\r
- /* return NULL if the maximum number of allocations was reached */\r
- if ( table->bound_count &&\r
- table->alloc_count >= table->alloc_count_max )\r
- return NULL;\r
-\r
- delta = (FT_Long)( new_size - cur_size );\r
-\r
- /* return NULL if this allocation would overflow the maximum heap size */\r
- if ( delta > 0 &&\r
- table->bound_total &&\r
- table->alloc_current + (FT_ULong)delta > table->alloc_total_max )\r
- return NULL;\r
-\r
- new_block = (FT_Byte *)ft_mem_table_alloc( table, new_size );\r
- if ( new_block == NULL )\r
- return NULL;\r
-\r
- ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );\r
-\r
- ft_memcpy( new_block, block, cur_size < new_size ? cur_size : new_size );\r
-\r
- ft_mem_table_remove( table, (FT_Byte*)block, delta );\r
-\r
- _ft_debug_file = "<unknown>";\r
- _ft_debug_lineno = 0;\r
-\r
- if ( !table->keep_alive )\r
- ft_mem_table_free( table, block );\r
-\r
- return new_block;\r
- }\r
-\r
-\r
- extern FT_Int\r
- ft_mem_debug_init( FT_Memory memory )\r
- {\r
- FT_MemTable table;\r
- FT_Int result = 0;\r
-\r
-\r
- if ( getenv( "FT2_DEBUG_MEMORY" ) )\r
- {\r
- table = ft_mem_table_new( memory );\r
- if ( table )\r
- {\r
- const char* p;\r
-\r
-\r
- memory->user = table;\r
- memory->alloc = ft_mem_debug_alloc;\r
- memory->realloc = ft_mem_debug_realloc;\r
- memory->free = ft_mem_debug_free;\r
-\r
- p = getenv( "FT2_ALLOC_TOTAL_MAX" );\r
- if ( p != NULL )\r
- {\r
- FT_Long total_max = ft_atol( p );\r
-\r
-\r
- if ( total_max > 0 )\r
- {\r
- table->bound_total = 1;\r
- table->alloc_total_max = (FT_ULong)total_max;\r
- }\r
- }\r
-\r
- p = getenv( "FT2_ALLOC_COUNT_MAX" );\r
- if ( p != NULL )\r
- {\r
- FT_Long total_count = ft_atol( p );\r
-\r
-\r
- if ( total_count > 0 )\r
- {\r
- table->bound_count = 1;\r
- table->alloc_count_max = (FT_ULong)total_count;\r
- }\r
- }\r
-\r
- p = getenv( "FT2_KEEP_ALIVE" );\r
- if ( p != NULL )\r
- {\r
- FT_Long keep_alive = ft_atol( p );\r
-\r
-\r
- if ( keep_alive > 0 )\r
- table->keep_alive = 1;\r
- }\r
-\r
- result = 1;\r
- }\r
- }\r
- return result;\r
- }\r
-\r
-\r
- extern void\r
- ft_mem_debug_done( FT_Memory memory )\r
- {\r
- FT_MemTable table = (FT_MemTable)memory->user;\r
-\r
-\r
- if ( table )\r
- {\r
- memory->free = table->free;\r
- memory->realloc = table->realloc;\r
- memory->alloc = table->alloc;\r
-\r
- ft_mem_table_destroy( table );\r
- memory->user = NULL;\r
- }\r
- }\r
-\r
-\r
-\r
- static int\r
- ft_mem_source_compare( const void* p1,\r
- const void* p2 )\r
- {\r
- FT_MemSource s1 = *(FT_MemSource*)p1;\r
- FT_MemSource s2 = *(FT_MemSource*)p2;\r
-\r
-\r
- if ( s2->max_size > s1->max_size )\r
- return 1;\r
- else if ( s2->max_size < s1->max_size )\r
- return -1;\r
- else\r
- return 0;\r
- }\r
-\r
-\r
- extern void\r
- FT_DumpMemory( FT_Memory memory )\r
- {\r
- FT_MemTable table = (FT_MemTable)memory->user;\r
-\r
-\r
- if ( table )\r
- {\r
- FT_MemSource* bucket = table->sources;\r
- FT_MemSource* limit = bucket + FT_MEM_SOURCE_BUCKETS;\r
- FT_MemSource* sources;\r
- FT_UInt nn, count;\r
- const char* fmt;\r
-\r
-\r
- count = 0;\r
- for ( ; bucket < limit; bucket++ )\r
- {\r
- FT_MemSource source = *bucket;\r
-\r
-\r
- for ( ; source; source = source->link )\r
- count++;\r
- }\r
-\r
- sources = (FT_MemSource*)ft_mem_table_alloc(\r
- table, sizeof ( *sources ) * count );\r
-\r
- count = 0;\r
- for ( bucket = table->sources; bucket < limit; bucket++ )\r
- {\r
- FT_MemSource source = *bucket;\r
-\r
-\r
- for ( ; source; source = source->link )\r
- sources[count++] = source;\r
- }\r
-\r
- ft_qsort( sources, count, sizeof ( *sources ), ft_mem_source_compare );\r
-\r
- printf( "FreeType Memory Dump: "\r
- "current=%ld max=%ld total=%ld count=%ld\n",\r
- table->alloc_current, table->alloc_max,\r
- table->alloc_total, table->alloc_count );\r
- printf( " block block sizes sizes sizes source\n" );\r
- printf( " count high sum highsum max location\n" );\r
- printf( "-------------------------------------------------\n" );\r
-\r
- fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";\r
-\r
- for ( nn = 0; nn < count; nn++ )\r
- {\r
- FT_MemSource source = sources[nn];\r
-\r
-\r
- printf( fmt,\r
- source->cur_blocks, source->max_blocks,\r
- source->cur_size, source->max_size, source->cur_max,\r
- FT_FILENAME( source->file_name ),\r
- source->line_no );\r
- }\r
- printf( "------------------------------------------------\n" );\r
-\r
- ft_mem_table_free( table, sources );\r
- }\r
- }\r
-\r
-#else /* !FT_DEBUG_MEMORY */\r
-\r
- /* ANSI C doesn't like empty source files */\r
- const FT_Byte _debug_mem_dummy = 0;\r
-\r
-#endif /* !FT_DEBUG_MEMORY */\r
-\r
-\r
-/* END */\r
+/***************************************************************************/
+/* */
+/* ftdbgmem.c */
+/* */
+/* Memory debugger (body). */
+/* */
+/* Copyright 2001, 2002, 2003, 2004, 2005, 2006 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 <stdio.h>
+#include <stdlib.h>
+
+ FT_BASE_DEF( const char* ) _ft_debug_file = 0;
+ 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_ULong)(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 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_ULong size;
+ FT_ULong nodes;
+ FT_MemNode* buckets;
+
+ FT_ULong alloc_total;
+ FT_ULong alloc_current;
+ FT_ULong alloc_max;
+ FT_ULong alloc_count;
+
+ FT_Bool bound_total;
+ FT_ULong alloc_total_max;
+
+ FT_Bool bound_count;
+ FT_ULong 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_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,
+ };
+
+
+ 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;
+ }
+
+
+ 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_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_ARRAY_ZERO( new_buckets, 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_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 * 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_ULong i;
+
+
+ FT_DumpMemory( table->memory );
+
+ if ( table )
+ {
+ FT_Long leak_count = 0;
+ FT_ULong leaks = 0;
+
+
+ /* 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 = 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->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] = 0;
+ }
+
+ 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_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 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_ULong 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 > (FT_ULong)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 );
+ }
+ }
+
+
+ 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 );
+
+ /* 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 > (FT_ULong)size )
+ return NULL;
+
+ block = (FT_Byte *)ft_mem_table_alloc( table, size );
+ if ( block )
+ {
+ ft_mem_table_set( table, block, (FT_ULong)size, 0 );
+
+ table->alloc_count++;
+ }
+
+ _ft_debug_file = "<unknown>";
+ _ft_debug_lineno = 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( _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;
+ }
+
+
+ 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;
+ 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 = (FT_Long)( new_size - cur_size );
+
+ /* return NULL if this allocation would overflow the maximum heap size */
+ if ( delta > 0 &&
+ table->bound_total &&
+ table->alloc_current + (FT_ULong)delta > table->alloc_total_max )
+ return NULL;
+
+ new_block = (FT_Byte *)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 ? cur_size : 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 = (FT_ULong)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 = (FT_ULong)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_UInt 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, sizeof ( *sources ) * count );
+
+ count = 0;
+ for ( bucket = table->sources; bucket < limit; bucket++ )
+ {
+ FT_MemSource source = *bucket;
+
+
+ for ( ; source; source = source->link )
+ sources[count++] = source;
+ }
+
+ ft_qsort( sources, 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 */
+ const FT_Byte _debug_mem_dummy = 0;
+
+#endif /* !FT_DEBUG_MEMORY */
+
+
+/* END */