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 "../../include/ft2build.h"
20#include "../../include/freetype/config/ftconfig.h"
21#include "../../include/freetype/internal/ftdebug.h"
22#include "../../include/freetype/internal/ftmemory.h"
23#include "../../include/freetype/ftsystem.h"
24#include "../../include/freetype/fterrors.h"
25#include "../../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 "../../include/freetype/config/ftstdlib.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    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*
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
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
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
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
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
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
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
819  ft_mem_debug_init( FT_Memory  memory )
820  {
821    FT_MemTable  table;
822    FT_Int       result = 0;
823
824
825    if (1)/* XYQ: always enable ( 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
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
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
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