Imported Upstream version 2.13.2
[platform/upstream/freetype2.git] / src / base / ftdbgmem.c
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
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
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
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
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
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
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*
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
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
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
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
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
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
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
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
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 )
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
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 */