264f307e66
include/freetype/internal/ftmemory.h, src/base/ftdbgmem.c, src/base/ftutil.c: udpating the memory management functions and macros to safely deal with array size buffer overflows, this corresponds to attemps to allocate arrays that are too large. For an example, consider the following code: count = read_uint32_from_file(); array = malloc( sizeof(Item) * count ); for ( nn = 0; nn < count; nn++ ) array[nn] = read_item_from_file(); if 'count' is larger than FT_UINT_MAX/sizeof(Item), the multiplication will overflow and the array allocated will be smaller than the data read from the file. In this case, the heap will be trashed, and this can be used as a denial-of-service, or make the engine crash later. the FT_ARRAY_NEW and FT_ARRAY_RENEW macro now check that the new count is no more than FT_INT_MAX/item_size, otherwise, a new error, named 'FT_Err_Array_Too_Large' will be returned. note that the memory debugger now works again when FT_DEBUG_MEMORY is defined, and FT_STRICT_ALIASING has disappeared, the corresponding code being now the default.
994 lines
24 KiB
C
994 lines
24 KiB
C
/***************************************************************************/
|
|
/* */
|
|
/* 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;
|
|
|
|
|
|
hash = (FT_UInt32)(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 */
|