1/***************************************************************************/
2/*                                                                         */
3/*  ftdbgmem.c                                                             */
4/*                                                                         */
5/*    Memory debugger (body).                                              */
6/*                                                                         */
7/*  Copyright 2001, 2002, 2003, 2004, 2005, 2006, 2009 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
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
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_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
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
344  ft_mem_table_destroy( FT_MemTable  table )
345  {
346    FT_ULong  i;
347
348
349    FT_DumpMemory( table->memory );
350
351    if ( table )
352    {
353      FT_Long   leak_count = 0;
354      FT_ULong  leaks      = 0;
355
356
357      /* remove all blocks from the table, revealing leaked ones */
358      for ( i = 0; i < table->size; i++ )
359      {
360        FT_MemNode  *pnode = table->buckets + i, next, node = *pnode;
361
362
363        while ( node )
364        {
365          next       = node->link;
366          node->link = 0;
367
368          if ( node->size > 0 )
369          {
370            printf(
371              "leaked memory block at address %p, size %8ld in (%s:%ld)\n",
372              node->address, node->size,
373              FT_FILENAME( node->source->file_name ),
374              node->source->line_no );
375
376            leak_count++;
377            leaks += node->size;
378
379            ft_mem_table_free( table, node->address );
380          }
381
382          node->address = NULL;
383          node->size    = 0;
384
385          ft_mem_table_free( table, node );
386          node = next;
387        }
388        table->buckets[i] = 0;
389      }
390
391      ft_mem_table_free( table, table->buckets );
392      table->buckets = NULL;
393
394      table->size  = 0;
395      table->nodes = 0;
396
397      /* remove all sources */
398      for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ )
399      {
400        FT_MemSource  source, next;
401
402
403        for ( source = table->sources[i]; source != NULL; source = next )
404        {
405          next = source->link;
406          ft_mem_table_free( table, source );
407        }
408
409        table->sources[i] = NULL;
410      }
411
412      printf(
413        "FreeType: total memory allocations = %ld\n", table->alloc_total );
414      printf(
415        "FreeType: maximum memory footprint = %ld\n", table->alloc_max );
416
417      ft_mem_table_free( table, table );
418
419      if ( leak_count > 0 )
420        ft_mem_debug_panic(
421          "FreeType: %ld bytes of memory leaked in %ld blocks\n",
422          leaks, leak_count );
423
424      printf( "FreeType: no memory leaks detected\n" );
425    }
426  }
427
428
429  static FT_MemNode*
430  ft_mem_table_get_nodep( FT_MemTable  table,
431                          FT_Byte*     address )
432  {
433    FT_PtrDist   hash;
434    FT_MemNode  *pnode, node;
435
436
437    hash  = FT_MEM_VAL( address );
438    pnode = table->buckets + ( hash % table->size );
439
440    for (;;)
441    {
442      node = pnode[0];
443      if ( !node )
444        break;
445
446      if ( node->address == address )
447        break;
448
449      pnode = &node->link;
450    }
451    return pnode;
452  }
453
454
455  static FT_MemSource
456  ft_mem_table_get_source( FT_MemTable  table )
457  {
458    FT_UInt32     hash;
459    FT_MemSource  node, *pnode;
460
461
462    /* cast to FT_PtrDist first since void* can be larger */
463    /* than FT_UInt32 and GCC 4.1.1 emits a warning       */
464    hash  = (FT_UInt32)(FT_PtrDist)(void*)_ft_debug_file +
465              (FT_UInt32)( 5 * _ft_debug_lineno );
466    pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS];
467
468    for ( ;; )
469    {
470      node = *pnode;
471      if ( node == NULL )
472        break;
473
474      if ( node->file_name == _ft_debug_file &&
475           node->line_no   == _ft_debug_lineno   )
476        goto Exit;
477
478      pnode = &node->link;
479    }
480
481    node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) );
482    if ( node == NULL )
483      ft_mem_debug_panic(
484        "not enough memory to perform memory debugging\n" );
485
486    node->file_name = _ft_debug_file;
487    node->line_no   = _ft_debug_lineno;
488
489    node->cur_blocks = 0;
490    node->max_blocks = 0;
491    node->all_blocks = 0;
492
493    node->cur_size   = 0;
494    node->max_size   = 0;
495    node->all_size   = 0;
496
497    node->cur_max    = 0;
498
499    node->link = NULL;
500    node->hash = hash;
501    *pnode     = node;
502
503  Exit:
504    return node;
505  }
506
507
508  static void
509  ft_mem_table_set( FT_MemTable  table,
510                    FT_Byte*     address,
511                    FT_ULong     size,
512                    FT_Long      delta )
513  {
514    FT_MemNode  *pnode, node;
515
516
517    if ( table )
518    {
519      FT_MemSource  source;
520
521
522      pnode = ft_mem_table_get_nodep( table, address );
523      node  = *pnode;
524      if ( node )
525      {
526        if ( node->size < 0 )
527        {
528          /* This block was already freed.  Our memory is now completely */
529          /* corrupted!                                                  */
530          /* This can only happen in keep-alive mode.                    */
531          ft_mem_debug_panic(
532            "memory heap corrupted (allocating freed block)" );
533        }
534        else
535        {
536          /* This block was already allocated.  This means that our memory */
537          /* is also corrupted!                                            */
538          ft_mem_debug_panic(
539            "memory heap corrupted (re-allocating allocated block at"
540            " %p, of size %ld)\n"
541            "org=%s:%d new=%s:%d\n",
542            node->address, node->size,
543            FT_FILENAME( node->source->file_name ), node->source->line_no,
544            FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );
545        }
546      }
547
548      /* we need to create a new node in this table */
549      node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) );
550      if ( node == NULL )
551        ft_mem_debug_panic( "not enough memory to run memory tests" );
552
553      node->address = address;
554      node->size    = size;
555      node->source  = source = ft_mem_table_get_source( table );
556
557      if ( delta == 0 )
558      {
559        /* this is an allocation */
560        source->all_blocks++;
561        source->cur_blocks++;
562        if ( source->cur_blocks > source->max_blocks )
563          source->max_blocks = source->cur_blocks;
564      }
565
566      if ( size > (FT_ULong)source->cur_max )
567        source->cur_max = size;
568
569      if ( delta != 0 )
570      {
571        /* we are growing or shrinking a reallocated block */
572        source->cur_size     += delta;
573        table->alloc_current += delta;
574      }
575      else
576      {
577        /* we are allocating a new block */
578        source->cur_size     += size;
579        table->alloc_current += size;
580      }
581
582      source->all_size += size;
583
584      if ( source->cur_size > source->max_size )
585        source->max_size = source->cur_size;
586
587      node->free_file_name = NULL;
588      node->free_line_no   = 0;
589
590      node->link = pnode[0];
591
592      pnode[0] = node;
593      table->nodes++;
594
595      table->alloc_total += size;
596
597      if ( table->alloc_current > table->alloc_max )
598        table->alloc_max = table->alloc_current;
599
600      if ( table->nodes * 3 < table->size  ||
601           table->size  * 3 < table->nodes )
602        ft_mem_table_resize( table );
603    }
604  }
605
606
607  static void
608  ft_mem_table_remove( FT_MemTable  table,
609                       FT_Byte*     address,
610                       FT_Long      delta )
611  {
612    if ( table )
613    {
614      FT_MemNode  *pnode, node;
615
616
617      pnode = ft_mem_table_get_nodep( table, address );
618      node  = *pnode;
619      if ( node )
620      {
621        FT_MemSource  source;
622
623
624        if ( node->size < 0 )
625          ft_mem_debug_panic(
626            "freeing memory block at %p more than once at (%s:%ld)\n"
627            "block allocated at (%s:%ld) and released at (%s:%ld)",
628            address,
629            FT_FILENAME( _ft_debug_file ), _ft_debug_lineno,
630            FT_FILENAME( node->source->file_name ), node->source->line_no,
631            FT_FILENAME( node->free_file_name ), node->free_line_no );
632
633        /* scramble the node's content for additional safety */
634        FT_MEM_SET( address, 0xF3, node->size );
635
636        if ( delta == 0 )
637        {
638          source = node->source;
639
640          source->cur_blocks--;
641          source->cur_size -= node->size;
642
643          table->alloc_current -= node->size;
644        }
645
646        if ( table->keep_alive )
647        {
648          /* we simply invert the node's size to indicate that the node */
649          /* was freed.                                                 */
650          node->size           = -node->size;
651          node->free_file_name = _ft_debug_file;
652          node->free_line_no   = _ft_debug_lineno;
653        }
654        else
655        {
656          table->nodes--;
657
658          *pnode = node->link;
659
660          node->size   = 0;
661          node->source = NULL;
662
663          ft_mem_table_free( table, node );
664
665          if ( table->nodes * 3 < table->size  ||
666               table->size  * 3 < table->nodes )
667            ft_mem_table_resize( table );
668        }
669      }
670      else
671        ft_mem_debug_panic(
672          "trying to free unknown block at %p in (%s:%ld)\n",
673          address,
674          FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );
675    }
676  }
677
678
679  extern FT_Pointer
680  ft_mem_debug_alloc( FT_Memory  memory,
681                      FT_Long    size )
682  {
683    FT_MemTable  table = (FT_MemTable)memory->user;
684    FT_Byte*     block;
685
686
687    if ( size <= 0 )
688      ft_mem_debug_panic( "negative block size allocation (%ld)", size );
689
690    /* return NULL if the maximum number of allocations was reached */
691    if ( table->bound_count                           &&
692         table->alloc_count >= table->alloc_count_max )
693      return NULL;
694
695    /* return NULL if this allocation would overflow the maximum heap size */
696    if ( table->bound_total                                             &&
697         table->alloc_total_max - table->alloc_current > (FT_ULong)size )
698      return NULL;
699
700    block = (FT_Byte *)ft_mem_table_alloc( table, size );
701    if ( block )
702    {
703      ft_mem_table_set( table, block, (FT_ULong)size, 0 );
704
705      table->alloc_count++;
706    }
707
708    _ft_debug_file   = "<unknown>";
709    _ft_debug_lineno = 0;
710
711    return (FT_Pointer)block;
712  }
713
714
715  extern void
716  ft_mem_debug_free( FT_Memory   memory,
717                     FT_Pointer  block )
718  {
719    FT_MemTable  table = (FT_MemTable)memory->user;
720
721
722    if ( block == NULL )
723      ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",
724                          FT_FILENAME( _ft_debug_file ),
725                          _ft_debug_lineno );
726
727    ft_mem_table_remove( table, (FT_Byte*)block, 0 );
728
729    if ( !table->keep_alive )
730      ft_mem_table_free( table, block );
731
732    table->alloc_count--;
733
734    _ft_debug_file   = "<unknown>";
735    _ft_debug_lineno = 0;
736  }
737
738
739  extern FT_Pointer
740  ft_mem_debug_realloc( FT_Memory   memory,
741                        FT_Long     cur_size,
742                        FT_Long     new_size,
743                        FT_Pointer  block )
744  {
745    FT_MemTable  table = (FT_MemTable)memory->user;
746    FT_MemNode   node, *pnode;
747    FT_Pointer   new_block;
748    FT_Long      delta;
749
750    const char*  file_name = FT_FILENAME( _ft_debug_file );
751    FT_Long      line_no   = _ft_debug_lineno;
752
753
754    /* unlikely, but possible */
755    if ( new_size == cur_size )
756      return block;
757
758    /* the following is valid according to ANSI C */
759#if 0
760    if ( block == NULL || cur_size == 0 )
761      ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",
762                          file_name, line_no );
763#endif
764
765    /* while the following is allowed in ANSI C also, we abort since */
766    /* such case should be handled by FreeType.                      */
767    if ( new_size <= 0 )
768      ft_mem_debug_panic(
769        "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",
770        block, cur_size, file_name, line_no );
771
772    /* check `cur_size' value */
773    pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );
774    node  = *pnode;
775    if ( !node )
776      ft_mem_debug_panic(
777        "trying to reallocate unknown block at %p in (%s:%ld)",
778        block, file_name, line_no );
779
780    if ( node->size <= 0 )
781      ft_mem_debug_panic(
782        "trying to reallocate freed block at %p in (%s:%ld)",
783        block, file_name, line_no );
784
785    if ( node->size != cur_size )
786      ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is "
787                          "%ld instead of %ld in (%s:%ld)",
788                          block, cur_size, node->size, file_name, line_no );
789
790    /* return NULL if the maximum number of allocations was reached */
791    if ( table->bound_count                           &&
792         table->alloc_count >= table->alloc_count_max )
793      return NULL;
794
795    delta = (FT_Long)( new_size - cur_size );
796
797    /* return NULL if this allocation would overflow the maximum heap size */
798    if ( delta > 0                                                       &&
799         table->bound_total                                              &&
800         table->alloc_current + (FT_ULong)delta > table->alloc_total_max )
801      return NULL;
802
803    new_block = (FT_Byte *)ft_mem_table_alloc( table, new_size );
804    if ( new_block == NULL )
805      return NULL;
806
807    ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );
808
809    ft_memcpy( new_block, block, cur_size < new_size ? cur_size : new_size );
810
811    ft_mem_table_remove( table, (FT_Byte*)block, delta );
812
813    _ft_debug_file   = "<unknown>";
814    _ft_debug_lineno = 0;
815
816    if ( !table->keep_alive )
817      ft_mem_table_free( table, block );
818
819    return new_block;
820  }
821
822
823  extern FT_Int
824  ft_mem_debug_init( FT_Memory  memory )
825  {
826    FT_MemTable  table;
827    FT_Int       result = 0;
828
829
830    if ( getenv( "FT2_DEBUG_MEMORY" ) )
831    {
832      table = ft_mem_table_new( memory );
833      if ( table )
834      {
835        const char*  p;
836
837
838        memory->user    = table;
839        memory->alloc   = ft_mem_debug_alloc;
840        memory->realloc = ft_mem_debug_realloc;
841        memory->free    = ft_mem_debug_free;
842
843        p = getenv( "FT2_ALLOC_TOTAL_MAX" );
844        if ( p != NULL )
845        {
846          FT_Long   total_max = ft_atol( p );
847
848
849          if ( total_max > 0 )
850          {
851            table->bound_total     = 1;
852            table->alloc_total_max = (FT_ULong)total_max;
853          }
854        }
855
856        p = getenv( "FT2_ALLOC_COUNT_MAX" );
857        if ( p != NULL )
858        {
859          FT_Long  total_count = ft_atol( p );
860
861
862          if ( total_count > 0 )
863          {
864            table->bound_count     = 1;
865            table->alloc_count_max = (FT_ULong)total_count;
866          }
867        }
868
869        p = getenv( "FT2_KEEP_ALIVE" );
870        if ( p != NULL )
871        {
872          FT_Long  keep_alive = ft_atol( p );
873
874
875          if ( keep_alive > 0 )
876            table->keep_alive = 1;
877        }
878
879        result = 1;
880      }
881    }
882    return result;
883  }
884
885
886  extern void
887  ft_mem_debug_done( FT_Memory  memory )
888  {
889    FT_MemTable  table = (FT_MemTable)memory->user;
890
891
892    if ( table )
893    {
894      memory->free    = table->free;
895      memory->realloc = table->realloc;
896      memory->alloc   = table->alloc;
897
898      ft_mem_table_destroy( table );
899      memory->user = NULL;
900    }
901  }
902
903
904
905  static int
906  ft_mem_source_compare( const void*  p1,
907                         const void*  p2 )
908  {
909    FT_MemSource  s1 = *(FT_MemSource*)p1;
910    FT_MemSource  s2 = *(FT_MemSource*)p2;
911
912
913    if ( s2->max_size > s1->max_size )
914      return 1;
915    else if ( s2->max_size < s1->max_size )
916      return -1;
917    else
918      return 0;
919  }
920
921
922  extern void
923  FT_DumpMemory( FT_Memory  memory )
924  {
925    FT_MemTable  table = (FT_MemTable)memory->user;
926
927
928    if ( table )
929    {
930      FT_MemSource*  bucket = table->sources;
931      FT_MemSource*  limit  = bucket + FT_MEM_SOURCE_BUCKETS;
932      FT_MemSource*  sources;
933      FT_UInt        nn, count;
934      const char*    fmt;
935
936
937      count = 0;
938      for ( ; bucket < limit; bucket++ )
939      {
940        FT_MemSource  source = *bucket;
941
942
943        for ( ; source; source = source->link )
944          count++;
945      }
946
947      sources = (FT_MemSource*)ft_mem_table_alloc(
948                                 table, sizeof ( *sources ) * count );
949
950      count = 0;
951      for ( bucket = table->sources; bucket < limit; bucket++ )
952      {
953        FT_MemSource  source = *bucket;
954
955
956        for ( ; source; source = source->link )
957          sources[count++] = source;
958      }
959
960      ft_qsort( sources, count, sizeof ( *sources ), ft_mem_source_compare );
961
962      printf( "FreeType Memory Dump: "
963              "current=%ld max=%ld total=%ld count=%ld\n",
964              table->alloc_current, table->alloc_max,
965              table->alloc_total, table->alloc_count );
966      printf( " block  block    sizes    sizes    sizes   source\n" );
967      printf( " count   high      sum  highsum      max   location\n" );
968      printf( "-------------------------------------------------\n" );
969
970      fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";
971
972      for ( nn = 0; nn < count; nn++ )
973      {
974        FT_MemSource  source = sources[nn];
975
976
977        printf( fmt,
978                source->cur_blocks, source->max_blocks,
979                source->cur_size, source->max_size, source->cur_max,
980                FT_FILENAME( source->file_name ),
981                source->line_no );
982      }
983      printf( "------------------------------------------------\n" );
984
985      ft_mem_table_free( table, sources );
986    }
987  }
988
989#else  /* !FT_DEBUG_MEMORY */
990
991  /* ANSI C doesn't like empty source files */
992  typedef int  _debug_mem_dummy;
993
994#endif /* !FT_DEBUG_MEMORY */
995
996
997/* END */
998