1 /***************************************************************************/
2 /*                                                                         */
3 /*  ftdbgmem.c                                                             */
4 /*                                                                         */
5 /*    Memory debugger (body).                                              */
6 /*                                                                         */
7 /*  Copyright 2001-2006, 2009, 2013 by                                     */
8 /*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
9 /*                                                                         */
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.                                        */
15 /*                                                                         */
16 /***************************************************************************/
17 
18 
19 #include <ft2build.h>
20 #include FT_CONFIG_CONFIG_H
21 #include FT_INTERNAL_DEBUG_H
22 #include FT_INTERNAL_MEMORY_H
23 #include FT_SYSTEM_H
24 #include FT_ERRORS_H
25 #include FT_TYPES_H
26 
27 
28 #ifdef FT_DEBUG_MEMORY
29 
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
33                     * memory, however.
34                     */
35 
36 #include FT_CONFIG_STANDARD_LIBRARY_H
37 
38   FT_BASE_DEF( const char* )  _ft_debug_file   = 0;
39   FT_BASE_DEF( long )         _ft_debug_lineno = 0;
40 
41   extern void
42   FT_DumpMemory( FT_Memory  memory );
43 
44 
45   typedef struct FT_MemSourceRec_*  FT_MemSource;
46   typedef struct FT_MemNodeRec_*    FT_MemNode;
47   typedef struct FT_MemTableRec_*   FT_MemTable;
48 
49 
50 #define FT_MEM_VAL( addr )  ((FT_PtrDist)(FT_Pointer)( addr ))
51 
52   /*
53    *  This structure holds statistics for a single allocation/release
54    *  site.  This is useful to know where memory operations happen the
55    *  most.
56    */
57   typedef struct  FT_MemSourceRec_
58   {
59     const char*   file_name;
60     long          line_no;
61 
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   */
65 
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   */
69 
70     FT_Long       cur_max;      /* current maximum allocated size */
71 
72     FT_UInt32     hash;
73     FT_MemSource  link;
74 
75   } FT_MemSourceRec;
76 
77 
78   /*
79    *  We don't need a resizable array for the memory sources, because
80    *  their number is pretty limited within FreeType.
81    */
82 #define FT_MEM_SOURCE_BUCKETS  128
83 
84   /*
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 frees,
89    *  at the price of large memory footprint during execution.
90    */
91   typedef struct  FT_MemNodeRec_
92   {
93     FT_Byte*      address;
94     FT_Long       size;     /* < 0 if the block was freed */
95 
96     FT_MemSource  source;
97 
98 #ifdef KEEPALIVE
99     const char*   free_file_name;
100     FT_Long       free_line_no;
101 #endif
102 
103     FT_MemNode    link;
104 
105   } FT_MemNodeRec;
106 
107 
108   /*
109    *  The global structure, containing compound statistics and all hash
110    *  tables.
111    */
112   typedef struct  FT_MemTableRec_
113   {
114     FT_ULong         size;
115     FT_ULong         nodes;
116     FT_MemNode*      buckets;
117 
118     FT_ULong         alloc_total;
119     FT_ULong         alloc_current;
120     FT_ULong         alloc_max;
121     FT_ULong         alloc_count;
122 
123     FT_Bool          bound_total;
124     FT_ULong         alloc_total_max;
125 
126     FT_Bool          bound_count;
127     FT_ULong         alloc_count_max;
128 
129     FT_MemSource     sources[FT_MEM_SOURCE_BUCKETS];
130 
131     FT_Bool          keep_alive;
132 
133     FT_Memory        memory;
134     FT_Pointer       memory_user;
135     FT_Alloc_Func    alloc;
136     FT_Free_Func     free;
137     FT_Realloc_Func  realloc;
138 
139   } FT_MemTableRec;
140 
141 
142 #define FT_MEM_SIZE_MIN  7
143 #define FT_MEM_SIZE_MAX  13845163
144 
145 #define FT_FILENAME( x )  ((x) ? (x) : "unknown file")
146 
147 
148   /*
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.
151    */
152   static const FT_UInt  ft_mem_primes[] =
153   {
154     7,
155     11,
156     19,
157     37,
158     73,
159     109,
160     163,
161     251,
162     367,
163     557,
164     823,
165     1237,
166     1861,
167     2777,
168     4177,
169     6247,
170     9371,
171     14057,
172     21089,
173     31627,
174     47431,
175     71143,
176     106721,
177     160073,
178     240101,
179     360163,
180     540217,
181     810343,
182     1215497,
183     1823231,
184     2734867,
185     4102283,
186     6153409,
187     9230113,
188     13845163,
189   };
190 
191 
192   static FT_ULong
ft_mem_closest_prime(FT_ULong num)193   ft_mem_closest_prime( FT_ULong  num )
194   {
195     FT_UInt  i;
196 
197 
198     for ( i = 0;
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];
202 
203     return FT_MEM_SIZE_MAX;
204   }
205 
206 
207   extern void
ft_mem_debug_panic(const char * fmt,...)208   ft_mem_debug_panic( const char*  fmt,
209                       ... )
210   {
211     va_list  ap;
212 
213 
214     printf( "FreeType.Debug: " );
215 
216     va_start( ap, fmt );
217     vprintf( fmt, ap );
218     va_end( ap );
219 
220     printf( "\n" );
221     exit( EXIT_FAILURE );
222   }
223 
224 
225   static FT_Pointer
ft_mem_table_alloc(FT_MemTable table,FT_Long size)226   ft_mem_table_alloc( FT_MemTable  table,
227                       FT_Long      size )
228   {
229     FT_Memory   memory = table->memory;
230     FT_Pointer  block;
231 
232 
233     memory->user = table->memory_user;
234     block = table->alloc( memory, size );
235     memory->user = table;
236 
237     return block;
238   }
239 
240 
241   static void
ft_mem_table_free(FT_MemTable table,FT_Pointer block)242   ft_mem_table_free( FT_MemTable  table,
243                      FT_Pointer   block )
244   {
245     FT_Memory  memory = table->memory;
246 
247 
248     memory->user = table->memory_user;
249     table->free( memory, block );
250     memory->user = table;
251   }
252 
253 
254   static void
ft_mem_table_resize(FT_MemTable table)255   ft_mem_table_resize( FT_MemTable  table )
256   {
257     FT_ULong  new_size;
258 
259 
260     new_size = ft_mem_closest_prime( table->nodes );
261     if ( new_size != table->size )
262     {
263       FT_MemNode*  new_buckets;
264       FT_ULong     i;
265 
266 
267       new_buckets = (FT_MemNode *)
268                       ft_mem_table_alloc( table,
269                                           new_size * sizeof ( FT_MemNode ) );
270       if ( new_buckets == NULL )
271         return;
272 
273       FT_ARRAY_ZERO( new_buckets, new_size );
274 
275       for ( i = 0; i < table->size; i++ )
276       {
277         FT_MemNode  node, next, *pnode;
278         FT_PtrDist  hash;
279 
280 
281         node = table->buckets[i];
282         while ( node )
283         {
284           next  = node->link;
285           hash  = FT_MEM_VAL( node->address ) % new_size;
286           pnode = new_buckets + hash;
287 
288           node->link = pnode[0];
289           pnode[0]   = node;
290 
291           node = next;
292         }
293       }
294 
295       if ( table->buckets )
296         ft_mem_table_free( table, table->buckets );
297 
298       table->buckets = new_buckets;
299       table->size    = new_size;
300     }
301   }
302 
303 
304   static FT_MemTable
ft_mem_table_new(FT_Memory memory)305   ft_mem_table_new( FT_Memory  memory )
306   {
307     FT_MemTable  table;
308 
309 
310     table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) );
311     if ( table == NULL )
312       goto Exit;
313 
314     FT_ZERO( table );
315 
316     table->size  = FT_MEM_SIZE_MIN;
317     table->nodes = 0;
318 
319     table->memory = memory;
320 
321     table->memory_user = memory->user;
322 
323     table->alloc   = memory->alloc;
324     table->realloc = memory->realloc;
325     table->free    = memory->free;
326 
327     table->buckets = (FT_MemNode *)
328                        memory->alloc( memory,
329                                       table->size * sizeof ( FT_MemNode ) );
330     if ( table->buckets )
331       FT_ARRAY_ZERO( table->buckets, table->size );
332     else
333     {
334       memory->free( memory, table );
335       table = NULL;
336     }
337 
338   Exit:
339     return table;
340   }
341 
342 
343   static void
ft_mem_table_destroy(FT_MemTable table)344   ft_mem_table_destroy( FT_MemTable  table )
345   {
346     FT_ULong  i;
347     FT_Long   leak_count = 0;
348     FT_ULong  leaks      = 0;
349 
350 
351     FT_DumpMemory( table->memory );
352 
353     /* remove all blocks from the table, revealing leaked ones */
354     for ( i = 0; i < table->size; i++ )
355     {
356       FT_MemNode  *pnode = table->buckets + i, next, node = *pnode;
357 
358 
359       while ( node )
360       {
361         next       = node->link;
362         node->link = 0;
363 
364         if ( node->size > 0 )
365         {
366           printf(
367             "leaked memory block at address %p, size %8ld in (%s:%ld)\n",
368             node->address, node->size,
369             FT_FILENAME( node->source->file_name ),
370             node->source->line_no );
371 
372           leak_count++;
373           leaks += node->size;
374 
375           ft_mem_table_free( table, node->address );
376         }
377 
378         node->address = NULL;
379         node->size    = 0;
380 
381         ft_mem_table_free( table, node );
382         node = next;
383       }
384       table->buckets[i] = 0;
385     }
386 
387     ft_mem_table_free( table, table->buckets );
388     table->buckets = NULL;
389 
390     table->size  = 0;
391     table->nodes = 0;
392 
393     /* remove all sources */
394     for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ )
395     {
396       FT_MemSource  source, next;
397 
398 
399       for ( source = table->sources[i]; source != NULL; source = next )
400       {
401         next = source->link;
402         ft_mem_table_free( table, source );
403       }
404 
405       table->sources[i] = NULL;
406     }
407 
408     printf( "FreeType: total memory allocations = %ld\n",
409             table->alloc_total );
410     printf( "FreeType: maximum memory footprint = %ld\n",
411             table->alloc_max );
412 
413     ft_mem_table_free( table, table );
414 
415     if ( leak_count > 0 )
416       ft_mem_debug_panic(
417         "FreeType: %ld bytes of memory leaked in %ld blocks\n",
418         leaks, leak_count );
419 
420     printf( "FreeType: no memory leaks detected\n" );
421   }
422 
423 
424   static FT_MemNode*
ft_mem_table_get_nodep(FT_MemTable table,FT_Byte * address)425   ft_mem_table_get_nodep( FT_MemTable  table,
426                           FT_Byte*     address )
427   {
428     FT_PtrDist   hash;
429     FT_MemNode  *pnode, node;
430 
431 
432     hash  = FT_MEM_VAL( address );
433     pnode = table->buckets + ( hash % table->size );
434 
435     for (;;)
436     {
437       node = pnode[0];
438       if ( !node )
439         break;
440 
441       if ( node->address == address )
442         break;
443 
444       pnode = &node->link;
445     }
446     return pnode;
447   }
448 
449 
450   static FT_MemSource
ft_mem_table_get_source(FT_MemTable table)451   ft_mem_table_get_source( FT_MemTable  table )
452   {
453     FT_UInt32     hash;
454     FT_MemSource  node, *pnode;
455 
456 
457     /* cast to FT_PtrDist first since void* can be larger */
458     /* than FT_UInt32 and GCC 4.1.1 emits a warning       */
459     hash  = (FT_UInt32)(FT_PtrDist)(void*)_ft_debug_file +
460               (FT_UInt32)( 5 * _ft_debug_lineno );
461     pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS];
462 
463     for ( ;; )
464     {
465       node = *pnode;
466       if ( node == NULL )
467         break;
468 
469       if ( node->file_name == _ft_debug_file &&
470            node->line_no   == _ft_debug_lineno   )
471         goto Exit;
472 
473       pnode = &node->link;
474     }
475 
476     node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) );
477     if ( node == NULL )
478       ft_mem_debug_panic(
479         "not enough memory to perform memory debugging\n" );
480 
481     node->file_name = _ft_debug_file;
482     node->line_no   = _ft_debug_lineno;
483 
484     node->cur_blocks = 0;
485     node->max_blocks = 0;
486     node->all_blocks = 0;
487 
488     node->cur_size   = 0;
489     node->max_size   = 0;
490     node->all_size   = 0;
491 
492     node->cur_max    = 0;
493 
494     node->link = NULL;
495     node->hash = hash;
496     *pnode     = node;
497 
498   Exit:
499     return node;
500   }
501 
502 
503   static void
ft_mem_table_set(FT_MemTable table,FT_Byte * address,FT_ULong size,FT_Long delta)504   ft_mem_table_set( FT_MemTable  table,
505                     FT_Byte*     address,
506                     FT_ULong     size,
507                     FT_Long      delta )
508   {
509     FT_MemNode  *pnode, node;
510 
511 
512     if ( table )
513     {
514       FT_MemSource  source;
515 
516 
517       pnode = ft_mem_table_get_nodep( table, address );
518       node  = *pnode;
519       if ( node )
520       {
521         if ( node->size < 0 )
522         {
523           /* This block was already freed.  Our memory is now completely */
524           /* corrupted!                                                  */
525           /* This can only happen in keep-alive mode.                    */
526           ft_mem_debug_panic(
527             "memory heap corrupted (allocating freed block)" );
528         }
529         else
530         {
531           /* This block was already allocated.  This means that our memory */
532           /* is also corrupted!                                            */
533           ft_mem_debug_panic(
534             "memory heap corrupted (re-allocating allocated block at"
535             " %p, of size %ld)\n"
536             "org=%s:%d new=%s:%d\n",
537             node->address, node->size,
538             FT_FILENAME( node->source->file_name ), node->source->line_no,
539             FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );
540         }
541       }
542 
543       /* we need to create a new node in this table */
544       node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) );
545       if ( node == NULL )
546         ft_mem_debug_panic( "not enough memory to run memory tests" );
547 
548       node->address = address;
549       node->size    = size;
550       node->source  = source = ft_mem_table_get_source( table );
551 
552       if ( delta == 0 )
553       {
554         /* this is an allocation */
555         source->all_blocks++;
556         source->cur_blocks++;
557         if ( source->cur_blocks > source->max_blocks )
558           source->max_blocks = source->cur_blocks;
559       }
560 
561       if ( size > (FT_ULong)source->cur_max )
562         source->cur_max = size;
563 
564       if ( delta != 0 )
565       {
566         /* we are growing or shrinking a reallocated block */
567         source->cur_size     += delta;
568         table->alloc_current += delta;
569       }
570       else
571       {
572         /* we are allocating a new block */
573         source->cur_size     += size;
574         table->alloc_current += size;
575       }
576 
577       source->all_size += size;
578 
579       if ( source->cur_size > source->max_size )
580         source->max_size = source->cur_size;
581 
582       node->free_file_name = NULL;
583       node->free_line_no   = 0;
584 
585       node->link = pnode[0];
586 
587       pnode[0] = node;
588       table->nodes++;
589 
590       table->alloc_total += size;
591 
592       if ( table->alloc_current > table->alloc_max )
593         table->alloc_max = table->alloc_current;
594 
595       if ( table->nodes * 3 < table->size  ||
596            table->size  * 3 < table->nodes )
597         ft_mem_table_resize( table );
598     }
599   }
600 
601 
602   static void
ft_mem_table_remove(FT_MemTable table,FT_Byte * address,FT_Long delta)603   ft_mem_table_remove( FT_MemTable  table,
604                        FT_Byte*     address,
605                        FT_Long      delta )
606   {
607     if ( table )
608     {
609       FT_MemNode  *pnode, node;
610 
611 
612       pnode = ft_mem_table_get_nodep( table, address );
613       node  = *pnode;
614       if ( node )
615       {
616         FT_MemSource  source;
617 
618 
619         if ( node->size < 0 )
620           ft_mem_debug_panic(
621             "freeing memory block at %p more than once at (%s:%ld)\n"
622             "block allocated at (%s:%ld) and released at (%s:%ld)",
623             address,
624             FT_FILENAME( _ft_debug_file ), _ft_debug_lineno,
625             FT_FILENAME( node->source->file_name ), node->source->line_no,
626             FT_FILENAME( node->free_file_name ), node->free_line_no );
627 
628         /* scramble the node's content for additional safety */
629         FT_MEM_SET( address, 0xF3, node->size );
630 
631         if ( delta == 0 )
632         {
633           source = node->source;
634 
635           source->cur_blocks--;
636           source->cur_size -= node->size;
637 
638           table->alloc_current -= node->size;
639         }
640 
641         if ( table->keep_alive )
642         {
643           /* we simply invert the node's size to indicate that the node */
644           /* was freed.                                                 */
645           node->size           = -node->size;
646           node->free_file_name = _ft_debug_file;
647           node->free_line_no   = _ft_debug_lineno;
648         }
649         else
650         {
651           table->nodes--;
652 
653           *pnode = node->link;
654 
655           node->size   = 0;
656           node->source = NULL;
657 
658           ft_mem_table_free( table, node );
659 
660           if ( table->nodes * 3 < table->size  ||
661                table->size  * 3 < table->nodes )
662             ft_mem_table_resize( table );
663         }
664       }
665       else
666         ft_mem_debug_panic(
667           "trying to free unknown block at %p in (%s:%ld)\n",
668           address,
669           FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );
670     }
671   }
672 
673 
674   extern FT_Pointer
ft_mem_debug_alloc(FT_Memory memory,FT_Long size)675   ft_mem_debug_alloc( FT_Memory  memory,
676                       FT_Long    size )
677   {
678     FT_MemTable  table = (FT_MemTable)memory->user;
679     FT_Byte*     block;
680 
681 
682     if ( size <= 0 )
683       ft_mem_debug_panic( "negative block size allocation (%ld)", size );
684 
685     /* return NULL if the maximum number of allocations was reached */
686     if ( table->bound_count                           &&
687          table->alloc_count >= table->alloc_count_max )
688       return NULL;
689 
690     /* return NULL if this allocation would overflow the maximum heap size */
691     if ( table->bound_total                                             &&
692          table->alloc_total_max - table->alloc_current > (FT_ULong)size )
693       return NULL;
694 
695     block = (FT_Byte *)ft_mem_table_alloc( table, size );
696     if ( block )
697     {
698       ft_mem_table_set( table, block, (FT_ULong)size, 0 );
699 
700       table->alloc_count++;
701     }
702 
703     _ft_debug_file   = "<unknown>";
704     _ft_debug_lineno = 0;
705 
706     return (FT_Pointer)block;
707   }
708 
709 
710   extern void
ft_mem_debug_free(FT_Memory memory,FT_Pointer block)711   ft_mem_debug_free( FT_Memory   memory,
712                      FT_Pointer  block )
713   {
714     FT_MemTable  table = (FT_MemTable)memory->user;
715 
716 
717     if ( block == NULL )
718       ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",
719                           FT_FILENAME( _ft_debug_file ),
720                           _ft_debug_lineno );
721 
722     ft_mem_table_remove( table, (FT_Byte*)block, 0 );
723 
724     if ( !table->keep_alive )
725       ft_mem_table_free( table, block );
726 
727     table->alloc_count--;
728 
729     _ft_debug_file   = "<unknown>";
730     _ft_debug_lineno = 0;
731   }
732 
733 
734   extern FT_Pointer
ft_mem_debug_realloc(FT_Memory memory,FT_Long cur_size,FT_Long new_size,FT_Pointer block)735   ft_mem_debug_realloc( FT_Memory   memory,
736                         FT_Long     cur_size,
737                         FT_Long     new_size,
738                         FT_Pointer  block )
739   {
740     FT_MemTable  table = (FT_MemTable)memory->user;
741     FT_MemNode   node, *pnode;
742     FT_Pointer   new_block;
743     FT_Long      delta;
744 
745     const char*  file_name = FT_FILENAME( _ft_debug_file );
746     FT_Long      line_no   = _ft_debug_lineno;
747 
748 
749     /* unlikely, but possible */
750     if ( new_size == cur_size )
751       return block;
752 
753     /* the following is valid according to ANSI C */
754 #if 0
755     if ( block == NULL || cur_size == 0 )
756       ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",
757                           file_name, line_no );
758 #endif
759 
760     /* while the following is allowed in ANSI C also, we abort since */
761     /* such case should be handled by FreeType.                      */
762     if ( new_size <= 0 )
763       ft_mem_debug_panic(
764         "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",
765         block, cur_size, file_name, line_no );
766 
767     /* check `cur_size' value */
768     pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );
769     node  = *pnode;
770     if ( !node )
771       ft_mem_debug_panic(
772         "trying to reallocate unknown block at %p in (%s:%ld)",
773         block, file_name, line_no );
774 
775     if ( node->size <= 0 )
776       ft_mem_debug_panic(
777         "trying to reallocate freed block at %p in (%s:%ld)",
778         block, file_name, line_no );
779 
780     if ( node->size != cur_size )
781       ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is "
782                           "%ld instead of %ld in (%s:%ld)",
783                           block, cur_size, node->size, file_name, line_no );
784 
785     /* return NULL if the maximum number of allocations was reached */
786     if ( table->bound_count                           &&
787          table->alloc_count >= table->alloc_count_max )
788       return NULL;
789 
790     delta = (FT_Long)( new_size - cur_size );
791 
792     /* return NULL if this allocation would overflow the maximum heap size */
793     if ( delta > 0                                                       &&
794          table->bound_total                                              &&
795          table->alloc_current + (FT_ULong)delta > table->alloc_total_max )
796       return NULL;
797 
798     new_block = (FT_Byte *)ft_mem_table_alloc( table, new_size );
799     if ( new_block == NULL )
800       return NULL;
801 
802     ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );
803 
804     ft_memcpy( new_block, block, cur_size < new_size ? cur_size : new_size );
805 
806     ft_mem_table_remove( table, (FT_Byte*)block, delta );
807 
808     _ft_debug_file   = "<unknown>";
809     _ft_debug_lineno = 0;
810 
811     if ( !table->keep_alive )
812       ft_mem_table_free( table, block );
813 
814     return new_block;
815   }
816 
817 
818   extern FT_Int
ft_mem_debug_init(FT_Memory memory)819   ft_mem_debug_init( FT_Memory  memory )
820   {
821     FT_MemTable  table;
822     FT_Int       result = 0;
823 
824 
825     if ( getenv( "FT2_DEBUG_MEMORY" ) )
826     {
827       table = ft_mem_table_new( memory );
828       if ( table )
829       {
830         const char*  p;
831 
832 
833         memory->user    = table;
834         memory->alloc   = ft_mem_debug_alloc;
835         memory->realloc = ft_mem_debug_realloc;
836         memory->free    = ft_mem_debug_free;
837 
838         p = getenv( "FT2_ALLOC_TOTAL_MAX" );
839         if ( p != NULL )
840         {
841           FT_Long   total_max = ft_atol( p );
842 
843 
844           if ( total_max > 0 )
845           {
846             table->bound_total     = 1;
847             table->alloc_total_max = (FT_ULong)total_max;
848           }
849         }
850 
851         p = getenv( "FT2_ALLOC_COUNT_MAX" );
852         if ( p != NULL )
853         {
854           FT_Long  total_count = ft_atol( p );
855 
856 
857           if ( total_count > 0 )
858           {
859             table->bound_count     = 1;
860             table->alloc_count_max = (FT_ULong)total_count;
861           }
862         }
863 
864         p = getenv( "FT2_KEEP_ALIVE" );
865         if ( p != NULL )
866         {
867           FT_Long  keep_alive = ft_atol( p );
868 
869 
870           if ( keep_alive > 0 )
871             table->keep_alive = 1;
872         }
873 
874         result = 1;
875       }
876     }
877     return result;
878   }
879 
880 
881   extern void
ft_mem_debug_done(FT_Memory memory)882   ft_mem_debug_done( FT_Memory  memory )
883   {
884     FT_MemTable  table = (FT_MemTable)memory->user;
885 
886 
887     if ( table )
888     {
889       memory->free    = table->free;
890       memory->realloc = table->realloc;
891       memory->alloc   = table->alloc;
892 
893       ft_mem_table_destroy( table );
894       memory->user = NULL;
895     }
896   }
897 
898 
899 
900   static int
ft_mem_source_compare(const void * p1,const void * p2)901   ft_mem_source_compare( const void*  p1,
902                          const void*  p2 )
903   {
904     FT_MemSource  s1 = *(FT_MemSource*)p1;
905     FT_MemSource  s2 = *(FT_MemSource*)p2;
906 
907 
908     if ( s2->max_size > s1->max_size )
909       return 1;
910     else if ( s2->max_size < s1->max_size )
911       return -1;
912     else
913       return 0;
914   }
915 
916 
917   extern void
FT_DumpMemory(FT_Memory memory)918   FT_DumpMemory( FT_Memory  memory )
919   {
920     FT_MemTable  table = (FT_MemTable)memory->user;
921 
922 
923     if ( table )
924     {
925       FT_MemSource*  bucket = table->sources;
926       FT_MemSource*  limit  = bucket + FT_MEM_SOURCE_BUCKETS;
927       FT_MemSource*  sources;
928       FT_UInt        nn, count;
929       const char*    fmt;
930 
931 
932       count = 0;
933       for ( ; bucket < limit; bucket++ )
934       {
935         FT_MemSource  source = *bucket;
936 
937 
938         for ( ; source; source = source->link )
939           count++;
940       }
941 
942       sources = (FT_MemSource*)ft_mem_table_alloc(
943                                  table, sizeof ( *sources ) * count );
944 
945       count = 0;
946       for ( bucket = table->sources; bucket < limit; bucket++ )
947       {
948         FT_MemSource  source = *bucket;
949 
950 
951         for ( ; source; source = source->link )
952           sources[count++] = source;
953       }
954 
955       ft_qsort( sources, count, sizeof ( *sources ), ft_mem_source_compare );
956 
957       printf( "FreeType Memory Dump: "
958               "current=%ld max=%ld total=%ld count=%ld\n",
959               table->alloc_current, table->alloc_max,
960               table->alloc_total, table->alloc_count );
961       printf( " block  block    sizes    sizes    sizes   source\n" );
962       printf( " count   high      sum  highsum      max   location\n" );
963       printf( "-------------------------------------------------\n" );
964 
965       fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";
966 
967       for ( nn = 0; nn < count; nn++ )
968       {
969         FT_MemSource  source = sources[nn];
970 
971 
972         printf( fmt,
973                 source->cur_blocks, source->max_blocks,
974                 source->cur_size, source->max_size, source->cur_max,
975                 FT_FILENAME( source->file_name ),
976                 source->line_no );
977       }
978       printf( "------------------------------------------------\n" );
979 
980       ft_mem_table_free( table, sources );
981     }
982   }
983 
984 #else  /* !FT_DEBUG_MEMORY */
985 
986   /* ANSI C doesn't like empty source files */
987   typedef int  _debug_mem_dummy;
988 
989 #endif /* !FT_DEBUG_MEMORY */
990 
991 
992 /* END */
993