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