ref: b280537b6d330a2e24307afc8058a79f5b3b8864
parent: a3c378024bfb412f021223bc7eca3d5030423060
author: David Turner <[email protected]>
date: Thu Mar 13 16:07:51 EST 2003
* 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.
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,25 @@
+2003-03-13 David Turner <[email protected]>
+
+ * 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 <[email protected]>
* src/gzip/ftgzip.c (ft_gzip_file_done): fixed memory leak, the ZLib
--- a/docs/DEBUG.TXT
+++ b/docs/DEBUG.TXT
@@ -159,4 +159,25 @@
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
--- a/src/base/ftdbgmem.c
+++ b/src/base/ftdbgmem.c
@@ -62,7 +62,14 @@
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;
}
}
--- 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,152 +562,237 @@
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 );
- if ( error )
- goto Exit;
-
- goto Skip;
+ 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;
}
-
- 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:
+ ;
}
- 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_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 )
{
- FTC_Node node;
+ 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;
+
-
- node = *pnode;
- if ( node == NULL )
- break;
-
- if ( node->hash == hash &&
- (FT_UInt)node->fam_index == family->fam_index &&
- compare( node, query, cache ) )
+ for ( ;; )
{
- /* move to head of bucket list */
- if ( pnode != bucket )
+ 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 ) )
{
- *pnode = node->link;
- node->link = *bucket;
- *bucket = node;
+ /* 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;
}
-
- /* move to head of MRU list */
- if ( node != cache->manager->nodes_list )
- ftc_node_mru_up( node, cache->manager );
-
- *anode = node;
- goto Exit;
+
+ pnode = &node->link;
}
-
- 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;
}
-
- /* didn't find a node, create a new one */
+
+ 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.
+ */
{
- FTC_Cache_Class clazz = cache->clazz;
- FTC_Manager manager = cache->manager;
- FT_Memory memory = cache->memory;
- FTC_Node node;
+ FT_UInt new_count;
+ new_count = 1 + free_count*2;
- if ( FT_ALLOC( node, clazz->node_size ) )
+ /* check overflow and bounds */
+ if ( new_count < free_count || free_count > manager->num_nodes )
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 )
+ free_count = new_count;
+
+ /* try to remove "new_count" nodes from the list */
{
- FT_FREE( node );
- goto Exit;
- }
+ FTC_Node first = manager->nodes_list;
+ FTC_Node node;
- error = ftc_node_hash_link( node, cache );
- if ( error )
- {
- clazz->node_done( node, cache );
- FT_FREE( node );
- goto Exit;
- }
+ if ( first == NULL ) /* empty list ! */
+ goto Exit;
- ftc_node_mru_link( node, cache->manager );
+ /* go to last node - it's a circular list */
+ node = first->mru_prev;
+ for ( ; node && new_count > 0; new_count-- )
+ {
+ FTC_Node prev = node->mru_prev;
- 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--;
- }
+ /* 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 )
+ {
+ /* if there are no unused nodes in the list, we'd better exit */
+ if ( new_count == free_count )
+ goto Exit;
+
+ break;
+ }
- *anode = node;
+ ftc_node_destroy( node, manager );
+
+ if ( node == first )
+ break;
+
+ node = prev;
+ }
+ }
}
}
--- 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 */
--- 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;
--- 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,78 +188,133 @@
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 )
--- 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;
}
--- 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 )
--- 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;
--- 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;
+ }
}