xref: /aosp_15_r20/external/freetype/src/base/ftdbgmem.c (revision 63949dbd25bcc50c4e1178497ff9e9574d44fc5a)
1 /****************************************************************************
2  *
3  * ftdbgmem.c
4  *
5  *   Memory debugger (body).
6  *
7  * Copyright (C) 2001-2023 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 <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>
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_   = NULL;
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
89    * frees, at the price of a 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_Long          size;
115     FT_Long          nodes;
116     FT_MemNode*      buckets;
117 
118     FT_Long          alloc_total;
119     FT_Long          alloc_current;
120     FT_Long          alloc_max;
121     FT_Long          alloc_count;
122 
123     FT_Bool          bound_total;
124     FT_Long          alloc_total_max;
125 
126     FT_Bool          bound_count;
127     FT_Long          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_Int  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_Long
ft_mem_closest_prime(FT_Long num)193   ft_mem_closest_prime( FT_Long  num )
194   {
195     size_t  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   static 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_Long  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_Long      i;
265 
266 
267       new_buckets = (FT_MemNode *)
268                       ft_mem_table_alloc(
269                         table,
270                         new_size * (FT_Long)sizeof ( FT_MemNode ) );
271       if ( !new_buckets )
272         return;
273 
274       FT_ARRAY_ZERO( new_buckets, new_size );
275 
276       for ( i = 0; i < table->size; i++ )
277       {
278         FT_MemNode  node, next, *pnode;
279         FT_PtrDist  hash;
280 
281 
282         node = table->buckets[i];
283         while ( node )
284         {
285           next  = node->link;
286           hash  = FT_MEM_VAL( node->address ) % (FT_PtrDist)new_size;
287           pnode = new_buckets + hash;
288 
289           node->link = pnode[0];
290           pnode[0]   = node;
291 
292           node = next;
293         }
294       }
295 
296       if ( table->buckets )
297         ft_mem_table_free( table, table->buckets );
298 
299       table->buckets = new_buckets;
300       table->size    = new_size;
301     }
302   }
303 
304 
305   static void
ft_mem_table_destroy(FT_MemTable table)306   ft_mem_table_destroy( FT_MemTable  table )
307   {
308     FT_Long  i;
309     FT_Long  leak_count = 0;
310     FT_Long  leaks      = 0;
311 
312 
313     /* remove all blocks from the table, revealing leaked ones */
314     for ( i = 0; i < table->size; i++ )
315     {
316       FT_MemNode  *pnode = table->buckets + i, next, node = *pnode;
317 
318 
319       while ( node )
320       {
321         next       = node->link;
322         node->link = NULL;
323 
324         if ( node->size > 0 )
325         {
326           printf(
327             "leaked memory block at address %p, size %8ld in (%s:%ld)\n",
328             (void*)node->address,
329             node->size,
330             FT_FILENAME( node->source->file_name ),
331             node->source->line_no );
332 
333           leak_count++;
334           leaks += node->size;
335 
336           ft_mem_table_free( table, node->address );
337         }
338 
339         node->address = NULL;
340         node->size    = 0;
341 
342         ft_mem_table_free( table, node );
343         node = next;
344       }
345       table->buckets[i] = NULL;
346     }
347 
348     ft_mem_table_free( table, table->buckets );
349     table->buckets = NULL;
350 
351     table->size  = 0;
352     table->nodes = 0;
353 
354     /* remove all sources */
355     for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ )
356     {
357       FT_MemSource  source, next;
358 
359 
360       for ( source = table->sources[i]; source != NULL; source = next )
361       {
362         next = source->link;
363         ft_mem_table_free( table, source );
364       }
365 
366       table->sources[i] = NULL;
367     }
368 
369     printf( "FreeType: total memory allocations = %ld\n",
370             table->alloc_total );
371     printf( "FreeType: maximum memory footprint = %ld\n",
372             table->alloc_max );
373 
374     if ( leak_count > 0 )
375       ft_mem_debug_panic(
376         "FreeType: %ld bytes of memory leaked in %ld blocks\n",
377         leaks, leak_count );
378 
379     printf( "FreeType: no memory leaks detected\n" );
380   }
381 
382 
383   static FT_MemNode*
ft_mem_table_get_nodep(FT_MemTable table,FT_Byte * address)384   ft_mem_table_get_nodep( FT_MemTable  table,
385                           FT_Byte*     address )
386   {
387     FT_PtrDist   hash;
388     FT_MemNode  *pnode, node;
389 
390 
391     hash  = FT_MEM_VAL( address );
392     pnode = table->buckets + ( hash % (FT_PtrDist)table->size );
393 
394     for (;;)
395     {
396       node = pnode[0];
397       if ( !node )
398         break;
399 
400       if ( node->address == address )
401         break;
402 
403       pnode = &node->link;
404     }
405     return pnode;
406   }
407 
408 
409   static FT_MemSource
ft_mem_table_get_source(FT_MemTable table)410   ft_mem_table_get_source( FT_MemTable  table )
411   {
412     FT_UInt32     hash;
413     FT_MemSource  node, *pnode;
414 
415 
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];
421 
422     for (;;)
423     {
424       node = *pnode;
425       if ( !node )
426         break;
427 
428       if ( node->file_name == ft_debug_file_   &&
429            node->line_no   == ft_debug_lineno_ )
430         goto Exit;
431 
432       pnode = &node->link;
433     }
434 
435     node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) );
436     if ( !node )
437       ft_mem_debug_panic(
438         "not enough memory to perform memory debugging\n" );
439 
440     node->file_name = ft_debug_file_;
441     node->line_no   = ft_debug_lineno_;
442 
443     node->cur_blocks = 0;
444     node->max_blocks = 0;
445     node->all_blocks = 0;
446 
447     node->cur_size = 0;
448     node->max_size = 0;
449     node->all_size = 0;
450 
451     node->cur_max = 0;
452 
453     node->link = NULL;
454     node->hash = hash;
455     *pnode     = node;
456 
457   Exit:
458     return node;
459   }
460 
461 
462   static void
ft_mem_table_set(FT_MemTable table,FT_Byte * address,FT_Long size,FT_Long delta)463   ft_mem_table_set( FT_MemTable  table,
464                     FT_Byte*     address,
465                     FT_Long      size,
466                     FT_Long      delta )
467   {
468     FT_MemNode  *pnode, node;
469 
470 
471     if ( table )
472     {
473       FT_MemSource  source;
474 
475 
476       pnode = ft_mem_table_get_nodep( table, address );
477       node  = *pnode;
478       if ( node )
479       {
480         if ( node->size < 0 )
481         {
482           /* This block was already freed.  Our memory is now completely */
483           /* corrupted!                                                  */
484           /* This can only happen in keep-alive mode.                    */
485           ft_mem_debug_panic(
486             "memory heap corrupted (allocating freed block)" );
487         }
488         else
489         {
490           /* This block was already allocated.  This means that our memory */
491           /* is also corrupted!                                            */
492           ft_mem_debug_panic(
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_ );
499         }
500       }
501 
502       /* we need to create a new node in this table */
503       node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) );
504       if ( !node )
505         ft_mem_debug_panic( "not enough memory to run memory tests" );
506 
507       node->address = address;
508       node->size    = size;
509       node->source  = source = ft_mem_table_get_source( table );
510 
511       if ( delta == 0 )
512       {
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;
518       }
519 
520       if ( size > source->cur_max )
521         source->cur_max = size;
522 
523       if ( delta != 0 )
524       {
525         /* we are growing or shrinking a reallocated block */
526         source->cur_size     += delta;
527         table->alloc_current += delta;
528       }
529       else
530       {
531         /* we are allocating a new block */
532         source->cur_size     += size;
533         table->alloc_current += size;
534       }
535 
536       source->all_size += size;
537 
538       if ( source->cur_size > source->max_size )
539         source->max_size = source->cur_size;
540 
541       node->free_file_name = NULL;
542       node->free_line_no   = 0;
543 
544       node->link = pnode[0];
545 
546       pnode[0] = node;
547       table->nodes++;
548 
549       table->alloc_total += size;
550 
551       if ( table->alloc_current > table->alloc_max )
552         table->alloc_max = table->alloc_current;
553 
554       if ( table->nodes * 3 < table->size  ||
555            table->size  * 3 < table->nodes )
556         ft_mem_table_resize( table );
557     }
558   }
559 
560 
561   static void
ft_mem_table_remove(FT_MemTable table,FT_Byte * address,FT_Long delta)562   ft_mem_table_remove( FT_MemTable  table,
563                        FT_Byte*     address,
564                        FT_Long      delta )
565   {
566     if ( table )
567     {
568       FT_MemNode  *pnode, node;
569 
570 
571       pnode = ft_mem_table_get_nodep( table, address );
572       node  = *pnode;
573       if ( node )
574       {
575         FT_MemSource  source;
576 
577 
578         if ( node->size < 0 )
579           ft_mem_debug_panic(
580             "freeing memory block at %p more than once\n"
581             "  at (%s:%ld)!\n"
582             "  Block was allocated at (%s:%ld)\n"
583             "  and released at (%s:%ld).",
584             address,
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 );
588 
589         /* scramble the node's content for additional safety */
590         FT_MEM_SET( address, 0xF3, node->size );
591 
592         if ( delta == 0 )
593         {
594           source = node->source;
595 
596           source->cur_blocks--;
597           source->cur_size -= node->size;
598 
599           table->alloc_current -= node->size;
600         }
601 
602         if ( table->keep_alive )
603         {
604           /* we simply invert the node's size to indicate that the node */
605           /* was freed.                                                 */
606           node->size           = -node->size;
607           node->free_file_name = ft_debug_file_;
608           node->free_line_no   = ft_debug_lineno_;
609         }
610         else
611         {
612           table->nodes--;
613 
614           *pnode = node->link;
615 
616           node->size   = 0;
617           node->source = NULL;
618 
619           ft_mem_table_free( table, node );
620 
621           if ( table->nodes * 3 < table->size  ||
622                table->size  * 3 < table->nodes )
623             ft_mem_table_resize( table );
624         }
625       }
626       else
627         ft_mem_debug_panic(
628           "trying to free unknown block at %p in (%s:%ld)\n",
629           address,
630           FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_ );
631     }
632   }
633 
634 
635   static FT_Pointer
ft_mem_debug_alloc(FT_Memory memory,FT_Long size)636   ft_mem_debug_alloc( FT_Memory  memory,
637                       FT_Long    size )
638   {
639     FT_MemTable  table = (FT_MemTable)memory->user;
640     FT_Byte*     block;
641 
642 
643     if ( size <= 0 )
644       ft_mem_debug_panic( "negative block size allocation (%ld)", size );
645 
646     /* return NULL if the maximum number of allocations was reached */
647     if ( table->bound_count                           &&
648          table->alloc_count >= table->alloc_count_max )
649       return NULL;
650 
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 )
654       return NULL;
655 
656     block = (FT_Byte *)ft_mem_table_alloc( table, size );
657     if ( block )
658     {
659       ft_mem_table_set( table, block, size, 0 );
660 
661       table->alloc_count++;
662     }
663 
664     ft_debug_file_   = "<unknown>";
665     ft_debug_lineno_ = 0;
666 
667     return (FT_Pointer)block;
668   }
669 
670 
671   static void
ft_mem_debug_free(FT_Memory memory,FT_Pointer block)672   ft_mem_debug_free( FT_Memory   memory,
673                      FT_Pointer  block )
674   {
675     FT_MemTable  table = (FT_MemTable)memory->user;
676 
677 
678     if ( !block )
679       ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",
680                           FT_FILENAME( ft_debug_file_ ),
681                           ft_debug_lineno_ );
682 
683     ft_mem_table_remove( table, (FT_Byte*)block, 0 );
684 
685     if ( !table->keep_alive )
686       ft_mem_table_free( table, block );
687 
688     table->alloc_count--;
689 
690     ft_debug_file_   = "<unknown>";
691     ft_debug_lineno_ = 0;
692   }
693 
694 
695   static FT_Pointer
ft_mem_debug_realloc(FT_Memory memory,FT_Long cur_size,FT_Long new_size,FT_Pointer block)696   ft_mem_debug_realloc( FT_Memory   memory,
697                         FT_Long     cur_size,
698                         FT_Long     new_size,
699                         FT_Pointer  block )
700   {
701     FT_MemTable  table = (FT_MemTable)memory->user;
702     FT_MemNode   node, *pnode;
703     FT_Pointer   new_block;
704     FT_Long      delta;
705 
706     const char*  file_name = FT_FILENAME( ft_debug_file_ );
707     FT_Long      line_no   = ft_debug_lineno_;
708 
709 
710     /* unlikely, but possible */
711     if ( new_size == cur_size )
712       return block;
713 
714     /* the following is valid according to ANSI C */
715 #if 0
716     if ( !block || !cur_size )
717       ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",
718                           file_name, line_no );
719 #endif
720 
721     /* while the following is allowed in ANSI C also, we abort since */
722     /* such case should be handled by FreeType.                      */
723     if ( new_size <= 0 )
724       ft_mem_debug_panic(
725         "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",
726         block, cur_size, file_name, line_no );
727 
728     /* check `cur_size' value */
729     pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );
730     node  = *pnode;
731     if ( !node )
732       ft_mem_debug_panic(
733         "trying to reallocate unknown block at %p in (%s:%ld)",
734         block, file_name, line_no );
735 
736     if ( node->size <= 0 )
737       ft_mem_debug_panic(
738         "trying to reallocate freed block at %p in (%s:%ld)",
739         block, file_name, line_no );
740 
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 );
745 
746     /* return NULL if the maximum number of allocations was reached */
747     if ( table->bound_count                           &&
748          table->alloc_count >= table->alloc_count_max )
749       return NULL;
750 
751     delta = new_size - cur_size;
752 
753     /* return NULL if this allocation would overflow the maximum heap size */
754     if ( delta > 0                                             &&
755          table->bound_total                                    &&
756          table->alloc_current + delta > table->alloc_total_max )
757       return NULL;
758 
759     new_block = (FT_Pointer)ft_mem_table_alloc( table, new_size );
760     if ( !new_block )
761       return NULL;
762 
763     ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );
764 
765     ft_memcpy( new_block, block, cur_size < new_size ? (size_t)cur_size
766                                                      : (size_t)new_size );
767 
768     ft_mem_table_remove( table, (FT_Byte*)block, delta );
769 
770     ft_debug_file_   = "<unknown>";
771     ft_debug_lineno_ = 0;
772 
773     if ( !table->keep_alive )
774       ft_mem_table_free( table, block );
775 
776     return new_block;
777   }
778 
779 
780   extern void
ft_mem_debug_init(FT_Memory memory)781   ft_mem_debug_init( FT_Memory  memory )
782   {
783     FT_MemTable  table;
784 
785 
786     if ( !ft_getenv( "FT2_DEBUG_MEMORY" ) )
787       return;
788 
789     table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) );
790 
791     if ( table )
792     {
793       FT_ZERO( table );
794 
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;
800 
801       ft_mem_table_resize( table );
802 
803       if ( table->size )
804       {
805         const char*  p;
806 
807 
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;
812 
813         p = ft_getenv( "FT2_ALLOC_TOTAL_MAX" );
814         if ( p )
815         {
816           FT_Long  total_max = ft_strtol( p, NULL, 10 );
817 
818 
819           if ( total_max > 0 )
820           {
821             table->bound_total     = 1;
822             table->alloc_total_max = total_max;
823           }
824         }
825 
826         p = ft_getenv( "FT2_ALLOC_COUNT_MAX" );
827         if ( p )
828         {
829           FT_Long  total_count = ft_strtol( p, NULL, 10 );
830 
831 
832           if ( total_count > 0 )
833           {
834             table->bound_count     = 1;
835             table->alloc_count_max = total_count;
836           }
837         }
838 
839         p = ft_getenv( "FT2_KEEP_ALIVE" );
840         if ( p )
841         {
842           FT_Long  keep_alive = ft_strtol( p, NULL, 10 );
843 
844 
845           if ( keep_alive > 0 )
846             table->keep_alive = 1;
847         }
848       }
849       else
850         memory->free( memory, table );
851     }
852   }
853 
854 
855   extern void
ft_mem_debug_done(FT_Memory memory)856   ft_mem_debug_done( FT_Memory  memory )
857   {
858     if ( memory->free == ft_mem_debug_free )
859     {
860       FT_MemTable  table = (FT_MemTable)memory->user;
861 
862 
863       FT_DumpMemory( memory );
864 
865       ft_mem_table_destroy( table );
866 
867       memory->free    = table->free;
868       memory->realloc = table->realloc;
869       memory->alloc   = table->alloc;
870       memory->user    = table->memory_user;
871 
872       memory->free( memory, table );
873     }
874   }
875 
876 
877   FT_COMPARE_DEF( int )
ft_mem_source_compare(const void * p1,const void * p2)878   ft_mem_source_compare( const void*  p1,
879                          const void*  p2 )
880   {
881     FT_MemSource  s1 = *(FT_MemSource*)p1;
882     FT_MemSource  s2 = *(FT_MemSource*)p2;
883 
884 
885     if ( s2->max_size > s1->max_size )
886       return 1;
887     else if ( s2->max_size < s1->max_size )
888       return -1;
889     else
890       return 0;
891   }
892 
893 
894   extern void
FT_DumpMemory(FT_Memory memory)895   FT_DumpMemory( FT_Memory  memory )
896   {
897     if ( memory->free == ft_mem_debug_free )
898     {
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;
903       FT_Int         nn, count;
904       const char*    fmt;
905 
906 
907       count = 0;
908       for ( ; bucket < limit; bucket++ )
909       {
910         FT_MemSource  source = *bucket;
911 
912 
913         for ( ; source; source = source->link )
914           count++;
915       }
916 
917       sources = (FT_MemSource*)
918                   ft_mem_table_alloc(
919                     table, count * (FT_Long)sizeof ( *sources ) );
920 
921       count = 0;
922       for ( bucket = table->sources; bucket < limit; bucket++ )
923       {
924         FT_MemSource  source = *bucket;
925 
926 
927         for ( ; source; source = source->link )
928           sources[count++] = source;
929       }
930 
931       ft_qsort( sources,
932                 (size_t)count,
933                 sizeof ( *sources ),
934                 ft_mem_source_compare );
935 
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" );
943 
944       fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";
945 
946       for ( nn = 0; nn < count; nn++ )
947       {
948         FT_MemSource  source = sources[nn];
949 
950 
951         printf( fmt,
952                 source->cur_blocks, source->max_blocks,
953                 source->cur_size, source->max_size, source->cur_max,
954                 FT_FILENAME( source->file_name ),
955                 source->line_no );
956       }
957       printf( "------------------------------------------------\n" );
958 
959       ft_mem_table_free( table, sources );
960     }
961   }
962 
963 #else  /* !FT_DEBUG_MEMORY */
964 
965   /* ANSI C doesn't like empty source files */
966   typedef int  debug_mem_dummy_;
967 
968 #endif /* !FT_DEBUG_MEMORY */
969 
970 
971 /* END */
972