1 /****************************************************************************
5 * Memory debugger (body).
7 * Copyright (C) 2001-2023 by
8 * David Turner, Robert Wilhelm, and Werner Lemberg.
10 * This file is part of the FreeType project, and may only be used,
11 * modified, and distributed under the terms of the FreeType project
12 * license, LICENSE.TXT. By continuing to use, modify, or distribute
13 * this file you indicate that you have read the license and
14 * understand and accept it fully.
20 #include FT_CONFIG_CONFIG_H
21 #include <freetype/internal/ftdebug.h>
22 #include <freetype/internal/ftmemory.h>
23 #include <freetype/ftsystem.h>
24 #include <freetype/fterrors.h>
25 #include <freetype/fttypes.h>
28 #ifdef FT_DEBUG_MEMORY
30 #define KEEPALIVE /* `Keep alive' means that freed blocks aren't released
31 * to the heap. This is useful to detect double-frees
32 * or weird heap corruption, but it uses large amounts of
36 #include FT_CONFIG_STANDARD_LIBRARY_H
38 FT_BASE_DEF( const char* ) ft_debug_file_ = NULL;
39 FT_BASE_DEF( long ) ft_debug_lineno_ = 0;
42 FT_DumpMemory( FT_Memory memory );
45 typedef struct FT_MemSourceRec_* FT_MemSource;
46 typedef struct FT_MemNodeRec_* FT_MemNode;
47 typedef struct FT_MemTableRec_* FT_MemTable;
50 #define FT_MEM_VAL( addr ) ( (FT_PtrDist)(FT_Pointer)( addr ) )
53 * This structure holds statistics for a single allocation/release
54 * site. This is useful to know where memory operations happen the
57 typedef struct FT_MemSourceRec_
59 const char* file_name;
62 FT_Long cur_blocks; /* current number of allocated blocks */
63 FT_Long max_blocks; /* max. number of allocated blocks */
64 FT_Long all_blocks; /* total number of blocks allocated */
66 FT_Long cur_size; /* current cumulative allocated size */
67 FT_Long max_size; /* maximum cumulative allocated size */
68 FT_Long all_size; /* total cumulative allocated size */
70 FT_Long cur_max; /* current maximum allocated size */
79 * We don't need a resizable array for the memory sources because
80 * their number is pretty limited within FreeType.
82 #define FT_MEM_SOURCE_BUCKETS 128
85 * This structure holds information related to a single allocated
86 * memory block. If KEEPALIVE is defined, blocks that are freed by
87 * FreeType are never released to the system. Instead, their `size'
88 * field is set to `-size'. This is mainly useful to detect double
89 * frees, at the price of a large memory footprint during execution.
91 typedef struct FT_MemNodeRec_
94 FT_Long size; /* < 0 if the block was freed */
99 const char* free_file_name;
100 FT_Long free_line_no;
109 * The global structure, containing compound statistics and all hash
112 typedef struct FT_MemTableRec_
119 FT_Long alloc_current;
124 FT_Long alloc_total_max;
127 FT_Long alloc_count_max;
129 FT_MemSource sources[FT_MEM_SOURCE_BUCKETS];
134 FT_Pointer memory_user;
137 FT_Realloc_Func realloc;
142 #define FT_MEM_SIZE_MIN 7
143 #define FT_MEM_SIZE_MAX 13845163
145 #define FT_FILENAME( x ) ( (x) ? (x) : "unknown file" )
149 * Prime numbers are ugly to handle. It would be better to implement
150 * L-Hashing, which is 10% faster and doesn't require divisions.
152 static const FT_Int ft_mem_primes[] =
193 ft_mem_closest_prime( FT_Long num )
199 i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ )
200 if ( ft_mem_primes[i] > num )
201 return ft_mem_primes[i];
203 return FT_MEM_SIZE_MAX;
208 ft_mem_debug_panic( const char* fmt,
214 printf( "FreeType.Debug: " );
221 exit( EXIT_FAILURE );
226 ft_mem_table_alloc( FT_MemTable table,
229 FT_Memory memory = table->memory;
233 memory->user = table->memory_user;
234 block = table->alloc( memory, size );
235 memory->user = table;
242 ft_mem_table_free( FT_MemTable table,
245 FT_Memory memory = table->memory;
248 memory->user = table->memory_user;
249 table->free( memory, block );
250 memory->user = table;
255 ft_mem_table_resize( FT_MemTable table )
260 new_size = ft_mem_closest_prime( table->nodes );
261 if ( new_size != table->size )
263 FT_MemNode* new_buckets;
267 new_buckets = (FT_MemNode *)
270 new_size * (FT_Long)sizeof ( FT_MemNode ) );
274 FT_ARRAY_ZERO( new_buckets, new_size );
276 for ( i = 0; i < table->size; i++ )
278 FT_MemNode node, next, *pnode;
282 node = table->buckets[i];
286 hash = FT_MEM_VAL( node->address ) % (FT_PtrDist)new_size;
287 pnode = new_buckets + hash;
289 node->link = pnode[0];
296 if ( table->buckets )
297 ft_mem_table_free( table, table->buckets );
299 table->buckets = new_buckets;
300 table->size = new_size;
306 ft_mem_table_destroy( FT_MemTable table )
309 FT_Long leak_count = 0;
313 /* remove all blocks from the table, revealing leaked ones */
314 for ( i = 0; i < table->size; i++ )
316 FT_MemNode *pnode = table->buckets + i, next, node = *pnode;
324 if ( node->size > 0 )
327 "leaked memory block at address %p, size %8ld in (%s:%ld)\n",
328 (void*)node->address,
330 FT_FILENAME( node->source->file_name ),
331 node->source->line_no );
336 ft_mem_table_free( table, node->address );
339 node->address = NULL;
342 ft_mem_table_free( table, node );
345 table->buckets[i] = NULL;
348 ft_mem_table_free( table, table->buckets );
349 table->buckets = NULL;
354 /* remove all sources */
355 for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ )
357 FT_MemSource source, next;
360 for ( source = table->sources[i]; source != NULL; source = next )
363 ft_mem_table_free( table, source );
366 table->sources[i] = NULL;
369 printf( "FreeType: total memory allocations = %ld\n",
370 table->alloc_total );
371 printf( "FreeType: maximum memory footprint = %ld\n",
374 if ( leak_count > 0 )
376 "FreeType: %ld bytes of memory leaked in %ld blocks\n",
379 printf( "FreeType: no memory leaks detected\n" );
384 ft_mem_table_get_nodep( FT_MemTable table,
388 FT_MemNode *pnode, node;
391 hash = FT_MEM_VAL( address );
392 pnode = table->buckets + ( hash % (FT_PtrDist)table->size );
400 if ( node->address == address )
410 ft_mem_table_get_source( FT_MemTable table )
413 FT_MemSource node, *pnode;
416 /* cast to FT_PtrDist first since void* can be larger */
417 /* than FT_UInt32 and GCC 4.1.1 emits a warning */
418 hash = (FT_UInt32)(FT_PtrDist)(void*)ft_debug_file_ +
419 (FT_UInt32)( 5 * ft_debug_lineno_ );
420 pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS];
428 if ( node->file_name == ft_debug_file_ &&
429 node->line_no == ft_debug_lineno_ )
435 node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) );
438 "not enough memory to perform memory debugging\n" );
440 node->file_name = ft_debug_file_;
441 node->line_no = ft_debug_lineno_;
443 node->cur_blocks = 0;
444 node->max_blocks = 0;
445 node->all_blocks = 0;
463 ft_mem_table_set( FT_MemTable table,
468 FT_MemNode *pnode, node;
476 pnode = ft_mem_table_get_nodep( table, address );
480 if ( node->size < 0 )
482 /* This block was already freed. Our memory is now completely */
484 /* This can only happen in keep-alive mode. */
486 "memory heap corrupted (allocating freed block)" );
490 /* This block was already allocated. This means that our memory */
491 /* is also corrupted! */
493 "memory heap corrupted (re-allocating allocated block at"
494 " %p, of size %ld)\n"
495 "org=%s:%d new=%s:%d\n",
496 node->address, node->size,
497 FT_FILENAME( node->source->file_name ), node->source->line_no,
498 FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_ );
502 /* we need to create a new node in this table */
503 node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) );
505 ft_mem_debug_panic( "not enough memory to run memory tests" );
507 node->address = address;
509 node->source = source = ft_mem_table_get_source( table );
513 /* this is an allocation */
514 source->all_blocks++;
515 source->cur_blocks++;
516 if ( source->cur_blocks > source->max_blocks )
517 source->max_blocks = source->cur_blocks;
520 if ( size > source->cur_max )
521 source->cur_max = size;
525 /* we are growing or shrinking a reallocated block */
526 source->cur_size += delta;
527 table->alloc_current += delta;
531 /* we are allocating a new block */
532 source->cur_size += size;
533 table->alloc_current += size;
536 source->all_size += size;
538 if ( source->cur_size > source->max_size )
539 source->max_size = source->cur_size;
541 node->free_file_name = NULL;
542 node->free_line_no = 0;
544 node->link = pnode[0];
549 table->alloc_total += size;
551 if ( table->alloc_current > table->alloc_max )
552 table->alloc_max = table->alloc_current;
554 if ( table->nodes * 3 < table->size ||
555 table->size * 3 < table->nodes )
556 ft_mem_table_resize( table );
562 ft_mem_table_remove( FT_MemTable table,
568 FT_MemNode *pnode, node;
571 pnode = ft_mem_table_get_nodep( table, address );
578 if ( node->size < 0 )
580 "freeing memory block at %p more than once\n"
582 " Block was allocated at (%s:%ld)\n"
583 " and released at (%s:%ld).",
585 FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_,
586 FT_FILENAME( node->source->file_name ), node->source->line_no,
587 FT_FILENAME( node->free_file_name ), node->free_line_no );
589 /* scramble the node's content for additional safety */
590 FT_MEM_SET( address, 0xF3, node->size );
594 source = node->source;
596 source->cur_blocks--;
597 source->cur_size -= node->size;
599 table->alloc_current -= node->size;
602 if ( table->keep_alive )
604 /* we simply invert the node's size to indicate that the node */
606 node->size = -node->size;
607 node->free_file_name = ft_debug_file_;
608 node->free_line_no = ft_debug_lineno_;
619 ft_mem_table_free( table, node );
621 if ( table->nodes * 3 < table->size ||
622 table->size * 3 < table->nodes )
623 ft_mem_table_resize( table );
628 "trying to free unknown block at %p in (%s:%ld)\n",
630 FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_ );
636 ft_mem_debug_alloc( FT_Memory memory,
639 FT_MemTable table = (FT_MemTable)memory->user;
644 ft_mem_debug_panic( "negative block size allocation (%ld)", size );
646 /* return NULL if the maximum number of allocations was reached */
647 if ( table->bound_count &&
648 table->alloc_count >= table->alloc_count_max )
651 /* return NULL if this allocation would overflow the maximum heap size */
652 if ( table->bound_total &&
653 table->alloc_total_max - table->alloc_current > size )
656 block = (FT_Byte *)ft_mem_table_alloc( table, size );
659 ft_mem_table_set( table, block, size, 0 );
661 table->alloc_count++;
664 ft_debug_file_ = "<unknown>";
665 ft_debug_lineno_ = 0;
667 return (FT_Pointer)block;
672 ft_mem_debug_free( FT_Memory memory,
675 FT_MemTable table = (FT_MemTable)memory->user;
679 ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",
680 FT_FILENAME( ft_debug_file_ ),
683 ft_mem_table_remove( table, (FT_Byte*)block, 0 );
685 if ( !table->keep_alive )
686 ft_mem_table_free( table, block );
688 table->alloc_count--;
690 ft_debug_file_ = "<unknown>";
691 ft_debug_lineno_ = 0;
696 ft_mem_debug_realloc( FT_Memory memory,
701 FT_MemTable table = (FT_MemTable)memory->user;
702 FT_MemNode node, *pnode;
703 FT_Pointer new_block;
706 const char* file_name = FT_FILENAME( ft_debug_file_ );
707 FT_Long line_no = ft_debug_lineno_;
710 /* unlikely, but possible */
711 if ( new_size == cur_size )
714 /* the following is valid according to ANSI C */
716 if ( !block || !cur_size )
717 ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",
718 file_name, line_no );
721 /* while the following is allowed in ANSI C also, we abort since */
722 /* such case should be handled by FreeType. */
725 "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",
726 block, cur_size, file_name, line_no );
728 /* check `cur_size' value */
729 pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );
733 "trying to reallocate unknown block at %p in (%s:%ld)",
734 block, file_name, line_no );
736 if ( node->size <= 0 )
738 "trying to reallocate freed block at %p in (%s:%ld)",
739 block, file_name, line_no );
741 if ( node->size != cur_size )
742 ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is "
743 "%ld instead of %ld in (%s:%ld)",
744 block, cur_size, node->size, file_name, line_no );
746 /* return NULL if the maximum number of allocations was reached */
747 if ( table->bound_count &&
748 table->alloc_count >= table->alloc_count_max )
751 delta = new_size - cur_size;
753 /* return NULL if this allocation would overflow the maximum heap size */
755 table->bound_total &&
756 table->alloc_current + delta > table->alloc_total_max )
759 new_block = (FT_Pointer)ft_mem_table_alloc( table, new_size );
763 ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );
765 ft_memcpy( new_block, block, cur_size < new_size ? (size_t)cur_size
766 : (size_t)new_size );
768 ft_mem_table_remove( table, (FT_Byte*)block, delta );
770 ft_debug_file_ = "<unknown>";
771 ft_debug_lineno_ = 0;
773 if ( !table->keep_alive )
774 ft_mem_table_free( table, block );
781 ft_mem_debug_init( FT_Memory memory )
786 if ( !ft_getenv( "FT2_DEBUG_MEMORY" ) )
789 table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) );
795 table->memory = memory;
796 table->memory_user = memory->user;
797 table->alloc = memory->alloc;
798 table->realloc = memory->realloc;
799 table->free = memory->free;
801 ft_mem_table_resize( table );
808 memory->user = table;
809 memory->alloc = ft_mem_debug_alloc;
810 memory->realloc = ft_mem_debug_realloc;
811 memory->free = ft_mem_debug_free;
813 p = ft_getenv( "FT2_ALLOC_TOTAL_MAX" );
816 FT_Long total_max = ft_strtol( p, NULL, 10 );
821 table->bound_total = 1;
822 table->alloc_total_max = total_max;
826 p = ft_getenv( "FT2_ALLOC_COUNT_MAX" );
829 FT_Long total_count = ft_strtol( p, NULL, 10 );
832 if ( total_count > 0 )
834 table->bound_count = 1;
835 table->alloc_count_max = total_count;
839 p = ft_getenv( "FT2_KEEP_ALIVE" );
842 FT_Long keep_alive = ft_strtol( p, NULL, 10 );
845 if ( keep_alive > 0 )
846 table->keep_alive = 1;
850 memory->free( memory, table );
856 ft_mem_debug_done( FT_Memory memory )
858 if ( memory->free == ft_mem_debug_free )
860 FT_MemTable table = (FT_MemTable)memory->user;
863 FT_DumpMemory( memory );
865 ft_mem_table_destroy( table );
867 memory->free = table->free;
868 memory->realloc = table->realloc;
869 memory->alloc = table->alloc;
870 memory->user = table->memory_user;
872 memory->free( memory, table );
877 FT_COMPARE_DEF( int )
878 ft_mem_source_compare( const void* p1,
881 FT_MemSource s1 = *(FT_MemSource*)p1;
882 FT_MemSource s2 = *(FT_MemSource*)p2;
885 if ( s2->max_size > s1->max_size )
887 else if ( s2->max_size < s1->max_size )
895 FT_DumpMemory( FT_Memory memory )
897 if ( memory->free == ft_mem_debug_free )
899 FT_MemTable table = (FT_MemTable)memory->user;
900 FT_MemSource* bucket = table->sources;
901 FT_MemSource* limit = bucket + FT_MEM_SOURCE_BUCKETS;
902 FT_MemSource* sources;
908 for ( ; bucket < limit; bucket++ )
910 FT_MemSource source = *bucket;
913 for ( ; source; source = source->link )
917 sources = (FT_MemSource*)
919 table, count * (FT_Long)sizeof ( *sources ) );
922 for ( bucket = table->sources; bucket < limit; bucket++ )
924 FT_MemSource source = *bucket;
927 for ( ; source; source = source->link )
928 sources[count++] = source;
934 ft_mem_source_compare );
936 printf( "FreeType Memory Dump: "
937 "current=%ld max=%ld total=%ld count=%ld\n",
938 table->alloc_current, table->alloc_max,
939 table->alloc_total, table->alloc_count );
940 printf( " block block sizes sizes sizes source\n" );
941 printf( " count high sum highsum max location\n" );
942 printf( "-------------------------------------------------\n" );
944 fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";
946 for ( nn = 0; nn < count; nn++ )
948 FT_MemSource source = sources[nn];
952 source->cur_blocks, source->max_blocks,
953 source->cur_size, source->max_size, source->cur_max,
954 FT_FILENAME( source->file_name ),
957 printf( "------------------------------------------------\n" );
959 ft_mem_table_free( table, sources );
963 #else /* !FT_DEBUG_MEMORY */
965 /* ANSI C doesn't like empty source files */
966 typedef int debug_mem_dummy_;
968 #endif /* !FT_DEBUG_MEMORY */