From ad1ceca2a5e831e474bee0209f816ebb55ca3e08 Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 13 Mar 2003 21:07:51 +0000 Subject: [PATCH] * src/base/ftdbgmem.c, docs/DEBUG.TXT: added new environment variables to control memory debugging with FreeType. See the description of "FT2_DEBUG_MEMORY", "FT2_ALLOC_TOTAL_MAX" and "FT2_ALLOC_COUNT_MAX" in DEBUG.TXT * src/cache/ftccache.c, src/cache/ftccmap.c, src/cache/ftcsbits.c, ftlru.c: fixed the cache sub-system to correctly deal with out-of-memory conditions. * src/pfr/pfrobjs.c, src/pfr/pfrsbits.c: fixing compiler warnings and a small memory leak * src/psaux/psobjs.c (t1_reallocate_table): fixed a bug (memory leak) that only happened when trying to resize an array would end in an OOM. * src/smooth/ftgrays.c: removed compiler warnings / volatile bug * src/truetype/ttobjs.c: removed segmentation fault that happened in tight memory environments. --- ChangeLog | 22 +++ docs/DEBUG.TXT | 21 +++ src/base/ftdbgmem.c | 48 +++++- src/cache/ftccache.c | 359 ++++++++++++++++++++++++++---------------- src/cache/ftccmap.c | 3 + src/cache/ftcsbits.c | 2 +- src/cache/ftlru.c | 156 ++++++++++++------ src/pfr/pfrobjs.c | 7 +- src/psaux/psobjs.c | 3 + src/smooth/ftgrays.c | 6 +- src/truetype/ttobjs.c | 17 +- 11 files changed, 445 insertions(+), 199 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8c2c416e5..409e3bda5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +2003-03-13 David Turner + + * src/base/ftdbgmem.c, docs/DEBUG.TXT: added new environment variables + to control memory debugging with FreeType. See the description of + "FT2_DEBUG_MEMORY", "FT2_ALLOC_TOTAL_MAX" and "FT2_ALLOC_COUNT_MAX" + in DEBUG.TXT + + * src/cache/ftccache.c, src/cache/ftccmap.c, src/cache/ftcsbits.c, + ftlru.c: fixed the cache sub-system to correctly deal with out-of-memory + conditions. + + * src/pfr/pfrobjs.c, src/pfr/pfrsbits.c: fixing compiler warnings and a + small memory leak + + * src/psaux/psobjs.c (t1_reallocate_table): fixed a bug (memory leak) that + only happened when trying to resize an array would end in an OOM. + + * src/smooth/ftgrays.c: removed compiler warnings / volatile bug + + * src/truetype/ttobjs.c: removed segmentation fault that happened in + tight memory environments. + 2003-02-28 Pixel * src/gzip/ftgzip.c (ft_gzip_file_done): fixed memory leak, the ZLib diff --git a/docs/DEBUG.TXT b/docs/DEBUG.TXT index ef7faa19e..8996d6690 100644 --- a/docs/DEBUG.TXT +++ b/docs/DEBUG.TXT @@ -159,4 +159,25 @@ behaviour of FreeType at runtime: ignored in other builds. + FT2_ALLOC_TOTAL_MAX + + this variable is ignored if FT2_DEBUG_MEMORY is not defined. It allows + you to specify a maximum heap size for all memory allocations performed + by FreeType. This is very useful to test the robustness of the font + engine and programs that use it in tight memory conditions. + + If it is undefined, or if its value is not strictly positive, then no + allocation bounds are checked at runtime. + + + FT2_ALLOC_COUNT_MAX + + this variable is ignored if FT2_DEBUG_MEMORY is not defined. It allows + you to sepcify a maximum number of memory allocations performed by + FreeType before returning the error FT_Err_Out_Of_Memory. This is + useful for debugging and testing the engine's robustness. + + If it is undefined, or if its value is not strictly positive, then no + allocation bounsd are checked at runtime. + End of file diff --git a/src/base/ftdbgmem.c b/src/base/ftdbgmem.c index 125b5b70a..15d186a20 100644 --- a/src/base/ftdbgmem.c +++ b/src/base/ftdbgmem.c @@ -62,6 +62,13 @@ 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; const char* file_name; FT_Long line_no; @@ -476,10 +483,22 @@ 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_current + (FT_ULong)size > table->alloc_total_max ) + return NULL; + block = (FT_Byte *)ft_mem_table_alloc( table, size ); if ( block ) ft_mem_table_set( table, block, (FT_ULong)size ); + table->alloc_count++; + table->file_name = NULL; table->line_no = 0; @@ -570,15 +589,42 @@ FT_Int result = 0; - if ( getenv( "FT_DEBUG_MEMORY" ) ) + 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 = 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 = atol(p); + + if ( total_count > 0 ) + { + table->bound_count = 1; + table->alloc_count_max = (FT_ULong) total_count; + } + } + result = 1; } } diff --git a/src/cache/ftccache.c b/src/cache/ftccache.c index 343f08eac..aba7ca4e1 100644 --- a/src/cache/ftccache.c +++ b/src/cache/ftccache.c @@ -357,10 +357,12 @@ FT_FREE( node ); +#if 0 /* check, just in case of general corruption :-) */ if ( manager->num_nodes == 0 ) FT_ERROR(( "ftc_node_destroy: invalid cache node count! = %d\n", manager->num_nodes )); +#endif } @@ -546,8 +548,10 @@ FTC_Query query, FTC_Node *anode ) { - FT_Error error = FT_Err_Ok; - FT_LruNode lru; + FT_Error error = FT_Err_Ok; + FTC_Manager manager; + FT_LruNode lru; + FT_UInt free_count = 0; if ( !cache || !query || !anode ) @@ -558,153 +562,238 @@ query->hash = 0; query->family = NULL; - /* XXX: we break encapsulation for the sake of speed! */ - { - /* first of all, find the relevant family */ - FT_LruList list = cache->families; - FT_LruNode fam, *pfam; - FT_LruNode_CompareFunc compare = list->clazz->node_compare; + manager = cache->manager; - pfam = &list->nodes; - for (;;) + /* here's a small note explaining what's hapenning in the code below. + * + * we need to deal intelligently with out-of-memory (OOM) conditions + * when trying to create a new family or cache node during the lookup. + * + * when an OOM is detected, we'll try to free one or more "old" nodes + * from the cache, then try again. it may be necessary to do that several + * times, so a loop is needed. + * + * the local variable "free_count" holds the number of "old" nodes to + * discard on each attempt. it starts at 1 and doubles on each iteration. + * the loop stops when: + * + * - a non-OOM error is detected + * - a succesful lookup is performed + * - there are no more unused nodes in the cache + * + * for the record, remember that all used nodes appear _before_ + * unused ones in the manager's MRU node list. + */ + + for (;;) + { { - fam = *pfam; - if ( fam == NULL ) + /* first of all, find the relevant family */ + FT_LruList list = cache->families; + FT_LruNode fam, *pfam; + FT_LruNode_CompareFunc compare = list->clazz->node_compare; + + pfam = &list->nodes; + for (;;) { - error = FT_LruList_Lookup( list, query, &lru ); + fam = *pfam; + if ( fam == NULL ) + { + error = FT_LruList_Lookup( list, query, &lru ); + if ( error ) + goto Fail; + + goto Skip; + } + + if ( compare( fam, query, list->data ) ) + break; + + pfam = &fam->next; + } + + FT_ASSERT( fam != NULL ); + + /* move to top of list when needed */ + if ( fam != list->nodes ) + { + *pfam = fam->next; + fam->next = list->nodes; + list->nodes = fam; + } + + lru = fam; + + Skip: + ; + } + + { + FTC_Manager manager = cache->manager; + FTC_Family family = (FTC_Family) lru; + FT_UFast hash = query->hash; + FTC_Node* bucket; + FT_UInt idx; + + + idx = hash & cache->mask; + if ( idx < cache->p ) + idx = hash & ( cache->mask * 2 + 1 ); + + bucket = cache->buckets + idx; + + + if ( query->family != family || + family->fam_index >= manager->families.size ) + { + FT_ERROR(( + "ftc_cache_lookup: invalid query (bad 'family' field)\n" )); + error = FTC_Err_Invalid_Argument; + goto Exit; + } + + if ( *bucket ) + { + FTC_Node* pnode = bucket; + FTC_Node_CompareFunc compare = cache->clazz->node_compare; + + + for ( ;; ) + { + FTC_Node node; + + + node = *pnode; + if ( node == NULL ) + break; + + if ( node->hash == hash && + (FT_UInt)node->fam_index == family->fam_index && + compare( node, query, cache ) ) + { + /* move to head of bucket list */ + if ( pnode != bucket ) + { + *pnode = node->link; + node->link = *bucket; + *bucket = node; + } + + /* move to head of MRU list */ + if ( node != manager->nodes_list ) + ftc_node_mru_up( node, manager ); + + *anode = node; + goto Exit; + } + + pnode = &node->link; + } + } + + /* didn't find a node, create a new one */ + { + FTC_Cache_Class clazz = cache->clazz; + FT_Memory memory = cache->memory; + FTC_Node node; + + + if ( FT_ALLOC( node, clazz->node_size ) ) + goto Fail; + + node->fam_index = (FT_UShort) family->fam_index; + node->hash = query->hash; + node->ref_count = 0; + + error = clazz->node_init( node, query, cache ); if ( error ) + { + FT_FREE( node ); + goto Fail; + } + + error = ftc_node_hash_link( node, cache ); + if ( error ) + { + clazz->node_done( node, cache ); + FT_FREE( node ); + goto Fail; + } + + ftc_node_mru_link( node, cache->manager ); + + cache->manager->cur_weight += clazz->node_weight( node, cache ); + + /* now try to compress the node pool when necessary */ + if ( manager->cur_weight >= manager->max_weight ) + { + node->ref_count++; + FTC_Manager_Compress( manager ); + node->ref_count--; + } + + *anode = node; + } + + /* all is well, exit now + */ + goto Exit; + } + + Fail: + if ( error != FT_Err_Out_Of_Memory ) + goto Exit; + + /* there is not enough memory, try to release some unused nodes + * from the cache to make room for a new one. + */ + { + FT_UInt new_count; + + new_count = 1 + free_count*2; + + /* check overflow and bounds */ + if ( new_count < free_count || free_count > manager->num_nodes ) + goto Exit; + + free_count = new_count; + + /* try to remove "new_count" nodes from the list */ + { + FTC_Node first = manager->nodes_list; + FTC_Node node; + + if ( first == NULL ) /* empty list ! */ goto Exit; - goto Skip; - } - - if ( compare( fam, query, list->data ) ) - break; - - pfam = &fam->next; - } - - FT_ASSERT( fam != NULL ); - - /* move to top of list when needed */ - if ( fam != list->nodes ) - { - *pfam = fam->next; - fam->next = list->nodes; - list->nodes = fam; - } - - lru = fam; - - Skip: - ; - } - - { - FTC_Family family = (FTC_Family) lru; - FT_UFast hash = query->hash; - FTC_Node* bucket; - FT_UInt idx; - - - idx = hash & cache->mask; - if ( idx < cache->p ) - idx = hash & ( cache->mask * 2 + 1 ); - - bucket = cache->buckets + idx; - - - if ( query->family != family || - family->fam_index >= cache->manager->families.size ) - { - FT_ERROR(( - "ftc_cache_lookup: invalid query (bad 'family' field)\n" )); - return FTC_Err_Invalid_Argument; - } - - if ( *bucket ) - { - FTC_Node* pnode = bucket; - FTC_Node_CompareFunc compare = cache->clazz->node_compare; - - - for ( ;; ) - { - FTC_Node node; - - - node = *pnode; - if ( node == NULL ) - break; - - if ( node->hash == hash && - (FT_UInt)node->fam_index == family->fam_index && - compare( node, query, cache ) ) + /* go to last node - it's a circular list */ + node = first->mru_prev; + for ( ; node && new_count > 0; new_count-- ) { - /* move to head of bucket list */ - if ( pnode != bucket ) + FTC_Node prev = node->mru_prev; + + /* used nodes always appear before unused one in the MRU + * list. if we find one here, we'd better stop right now + * our iteration + */ + if ( node->ref_count > 0 ) { - *pnode = node->link; - node->link = *bucket; - *bucket = node; + /* if there are no unused nodes in the list, we'd better exit */ + if ( new_count == free_count ) + goto Exit; + + break; } - /* move to head of MRU list */ - if ( node != cache->manager->nodes_list ) - ftc_node_mru_up( node, cache->manager ); + ftc_node_destroy( node, manager ); - *anode = node; - goto Exit; + if ( node == first ) + break; + + node = prev; } - - pnode = &node->link; } } - - /* didn't find a node, create a new one */ - { - FTC_Cache_Class clazz = cache->clazz; - FTC_Manager manager = cache->manager; - FT_Memory memory = cache->memory; - FTC_Node node; - - - if ( FT_ALLOC( node, clazz->node_size ) ) - goto Exit; - - node->fam_index = (FT_UShort) family->fam_index; - node->hash = query->hash; - node->ref_count = 0; - - error = clazz->node_init( node, query, cache ); - if ( error ) - { - FT_FREE( node ); - goto Exit; - } - - error = ftc_node_hash_link( node, cache ); - if ( error ) - { - clazz->node_done( node, cache ); - FT_FREE( node ); - goto Exit; - } - - ftc_node_mru_link( node, cache->manager ); - - cache->manager->cur_weight += clazz->node_weight( node, cache ); - - /* now try to compress the node pool when necessary */ - if ( manager->cur_weight >= manager->max_weight ) - { - node->ref_count++; - FTC_Manager_Compress( manager ); - node->ref_count--; - } - - *anode = node; - } } Exit: diff --git a/src/cache/ftccmap.c b/src/cache/ftccmap.c index 77e6ecbd5..8366ce574 100644 --- a/src/cache/ftccmap.c +++ b/src/cache/ftccmap.c @@ -27,6 +27,9 @@ #include "ftcerror.h" +#undef FT_COMPONENT +#define FT_COMPONENT trace_cache + /*************************************************************************/ /* */ /* Each FTC_CMapNode contains a simple array to map a range of character */ diff --git a/src/cache/ftcsbits.c b/src/cache/ftcsbits.c index 663467616..584038241 100644 --- a/src/cache/ftcsbits.c +++ b/src/cache/ftcsbits.c @@ -216,7 +216,7 @@ /* we mark unloaded glyphs with `sbit.buffer == 0' */ /* and 'width == 255', 'height == 0' */ /* */ - if ( error ) + if ( error && error != FT_Err_Out_Of_Memory ) { sbit->width = 255; error = 0; diff --git a/src/cache/ftlru.c b/src/cache/ftlru.c index 4a6f60355..47d656224 100644 --- a/src/cache/ftlru.c +++ b/src/cache/ftlru.c @@ -21,6 +21,7 @@ #include FT_CACHE_INTERNAL_LRU_H #include FT_LIST_H #include FT_INTERNAL_OBJECTS_H +#include FT_INTERNAL_DEBUG_H #include "ftcerror.h" @@ -187,80 +188,135 @@ goto Exit; } - /* we haven't found the relevant element. We will now try */ - /* to create a new one. */ - /* */ - - /* first, check if our list if full, when appropriate */ - if ( list->max_nodes > 0 && list->num_nodes >= list->max_nodes ) + /* since we haven't found the relevant element in our LRU list, + * we're going to "create" a new one. + * + * the following code is a bit special, because it tries to handle + * out-of-memory conditions (OOM) in an intelligent way. + * + * more precisely, if not enough memory is available to create a + * new node or "flush" an old one, we need to remove the oldest + * elements from our list, and try again. since several tries may + * be necessary, a loop is needed + * + * this loop will only exit when: + * + * - a new node was succesfully created, or an old node flushed + * - an error other than FT_Err_Out_Of_Memory is detected + * - the list of nodes is empty, and it isn't possible to create + * new nodes + * + * on each unsucesful attempt, one node will be removed from the list + * + */ + { - /* this list list is full; we will now flush */ - /* the oldest node, if there's one! */ - FT_LruNode last = *plast; + FT_Int drop_last = ( list->max_nodes > 0 && + list->num_nodes >= list->max_nodes ); - - if ( last ) + for (;;) { - if ( clazz->node_flush ) + node = NULL; + + /* when "drop_last" is true, we should free the last node in + * the list to make room for a new one. note that we re-use + * its memory block to save allocation calls. + */ + if ( drop_last ) { - error = clazz->node_flush( last, key, list->data ); + /* find the last node in the list + */ + pnode = &list->nodes; + node = *pnode; + + if ( node == NULL ) + { + FT_ASSERT( list->nodes == 0 ); + error = FT_Err_Out_Of_Memory; + goto Exit; + } + + FT_ASSERT( list->nodes > 0 ); + + while ( node->next ) + { + pnode = &node->next; + node = *pnode; + } + + /* remove it from the list, and try to "flush" it. doing this will + * save a significant number of dynamic allocations compared to + * a classic destroy/create cycle + */ + *pnode = NULL; + list->num_nodes -= 1; + + if ( clazz->node_flush ) + { + error = clazz->node_flush( node, key, list->data ); + if ( !error ) + goto Success; + + /* note that if an error occured during the flush, we need to + * finalize it since it is potentially in incomplete state. + */ + } + + /* we finalize, but do not destroy the last node, we + * simply re-use its memory block ! + */ + if ( clazz->node_done ) + clazz->node_done( node, list->data ); + + FT_MEM_ZERO( node, clazz->node_size ); } else { - if ( clazz->node_done ) - clazz->node_done( last, list->data ); - - last->key = key; - error = clazz->node_init( last, key, list->data ); + /* try to allocate a new node when "drop_last" is not TRUE + * this usually happens on the first pass, when the LRU list + * is not already full. + */ + if ( FT_ALLOC( node, clazz->node_size ) ) + goto Fail; } + + FT_ASSERT( node != NULL ); - if ( !error ) + node->key = key; + error = clazz->node_init( node, key, list->data ); + if ( error ) { - /* move it to the top of the list */ - *plast = NULL; - last->next = list->nodes; - list->nodes = last; + if ( clazz->node_done ) + clazz->node_done( node, list->data ); - result = last; - goto Exit; + FT_FREE( node ); + goto Fail; } - /* in case of error during the flush or done/init cycle, */ - /* we need to discard the node */ - if ( clazz->node_done ) - clazz->node_done( last, list->data ); + Success: + result = node; - *plast = NULL; - list->num_nodes--; - - FT_FREE( last ); + node->next = list->nodes; + list->nodes = node; + list->num_nodes++; goto Exit; + + Fail: + if ( error != FT_Err_Out_Of_Memory ) + goto Exit; + + drop_last = 1; + continue; } } - /* otherwise, simply allocate a new node */ - if ( FT_ALLOC( node, clazz->node_size ) ) - goto Exit; - - node->key = key; - error = clazz->node_init( node, key, list->data ); - if ( error ) - { - FT_FREE( node ); - goto Exit; - } - - result = node; - node->next = list->nodes; - list->nodes = node; - list->num_nodes++; - Exit: *anode = result; return error; } + FT_EXPORT_DEF( void ) FT_LruList_Remove( FT_LruList list, FT_LruNode node ) diff --git a/src/pfr/pfrobjs.c b/src/pfr/pfrobjs.c index 202184838..867644ab0 100644 --- a/src/pfr/pfrobjs.c +++ b/src/pfr/pfrobjs.c @@ -41,6 +41,8 @@ FT_LOCAL_DEF( void ) pfr_face_done( PFR_Face face ) { + FT_Memory memory = face->root.driver->root.memory; + /* we don't want dangling pointers */ face->root.family_name = NULL; face->root.style_name = NULL; @@ -49,6 +51,7 @@ pfr_phy_font_done( &face->phy_font, FT_FACE_MEMORY( face ) ); /* no need to finalize the logical font or the header */ + FT_FREE( face->root.available_sizes ); } @@ -179,8 +182,8 @@ strike = phy_font->strikes; for ( n = 0; n < count; n++, size++, strike++ ) { - size->height = strike->y_ppm; - size->width = strike->x_ppm; + size->height = (FT_UShort) strike->y_ppm; + size->width = (FT_UShort) strike->x_ppm; } root->num_fixed_sizes = count; } diff --git a/src/psaux/psobjs.c b/src/psaux/psobjs.c index 603595062..e930b7134 100644 --- a/src/psaux/psobjs.c +++ b/src/psaux/psobjs.c @@ -111,7 +111,10 @@ /* allocate new base block */ if ( FT_ALLOC( table->block, new_size ) ) + { + table->block = old_base; return error; + } /* copy elements and shift offsets */ if (old_base ) diff --git a/src/smooth/ftgrays.c b/src/smooth/ftgrays.c index 5ca7c7090..6c0948188 100644 --- a/src/smooth/ftgrays.c +++ b/src/smooth/ftgrays.c @@ -1827,9 +1827,9 @@ gray_convert_glyph( RAS_ARG ) { TBand bands[40]; - volatile TBand* band; - volatile int n, num_bands; - volatile TPos min, max, max_y; + TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; FT_BBox* clip; diff --git a/src/truetype/ttobjs.c b/src/truetype/ttobjs.c index 683564804..9d78baee7 100644 --- a/src/truetype/ttobjs.c +++ b/src/truetype/ttobjs.c @@ -70,14 +70,17 @@ { FT_Memory memory = zone->memory; + if ( memory ) + { + FT_FREE( zone->contours ); + FT_FREE( zone->tags ); + FT_FREE( zone->cur ); + FT_FREE( zone->org ); - FT_FREE( zone->contours ); - FT_FREE( zone->tags ); - FT_FREE( zone->cur ); - FT_FREE( zone->org ); - - zone->max_points = zone->n_points = 0; - zone->max_contours = zone->n_contours = 0; + zone->max_points = zone->n_points = 0; + zone->max_contours = zone->n_contours = 0; + zone->memory = NULL; + } }