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