1/****************************************************************************
2 *
3 * ftdbgmem.c
4 *
5 * Memory debugger (body).
6 *
7 * Copyright (C) 2001-2023 by
8 * David Turner, Robert Wilhelm, and Werner Lemberg.
9 *
10 * This file is part of the FreeType project, and may only be used,
11 * modified, and distributed under the terms of the FreeType project
12 * license, LICENSE.TXT. By continuing to use, modify, or distribute
13 * this file you indicate that you have read the license and
14 * understand and accept it fully.
15 *
16 */
17
18
19#include <ft2build.h>
20#include FT_CONFIG_CONFIG_H
21#include <freetype/internal/ftdebug.h>
22#include <freetype/internal/ftmemory.h>
23#include <freetype/ftsystem.h>
24#include <freetype/fterrors.h>
25#include <freetype/fttypes.h>
26
27
28#ifdef FT_DEBUG_MEMORY
29
30#define KEEPALIVE /* `Keep alive' means that freed blocks aren't released
31 * to the heap. This is useful to detect double-frees
32 * or weird heap corruption, but it uses large amounts of
33 * memory, however.
34 */
35
36#include FT_CONFIG_STANDARD_LIBRARY_H
37
38 FT_BASE_DEF( const char* ) ft_debug_file_ = NULL;
39 FT_BASE_DEF( long ) ft_debug_lineno_ = 0;
40
41 extern void
42 FT_DumpMemory( FT_Memory memory );
43
44
45 typedef struct FT_MemSourceRec_* FT_MemSource;
46 typedef struct FT_MemNodeRec_* FT_MemNode;
47 typedef struct FT_MemTableRec_* FT_MemTable;
48
49
50#define FT_MEM_VAL( addr ) ( (FT_PtrDist)(FT_Pointer)( addr ) )
51
52 /*
53 * This structure holds statistics for a single allocation/release
54 * site. This is useful to know where memory operations happen the
55 * most.
56 */
57 typedef struct FT_MemSourceRec_
58 {
59 const char* file_name;
60 long line_no;
61
62 FT_Long cur_blocks; /* current number of allocated blocks */
63 FT_Long max_blocks; /* max. number of allocated blocks */
64 FT_Long all_blocks; /* total number of blocks allocated */
65
66 FT_Long cur_size; /* current cumulative allocated size */
67 FT_Long max_size; /* maximum cumulative allocated size */
68 FT_Long all_size; /* total cumulative allocated size */
69
70 FT_Long cur_max; /* current maximum allocated size */
71
72 FT_UInt32 hash;
73 FT_MemSource link;
74
75 } FT_MemSourceRec;
76
77
78 /*
79 * We don't need a resizable array for the memory sources because
80 * their number is pretty limited within FreeType.
81 */
82#define FT_MEM_SOURCE_BUCKETS 128
83
84 /*
85 * This structure holds information related to a single allocated
86 * memory block. If KEEPALIVE is defined, blocks that are freed by
87 * FreeType are never released to the system. Instead, their `size'
88 * field is set to `-size'. This is mainly useful to detect double
89 * frees, at the price of a large memory footprint during execution.
90 */
91 typedef struct FT_MemNodeRec_
92 {
93 FT_Byte* address;
94 FT_Long size; /* < 0 if the block was freed */
95
96 FT_MemSource source;
97
98#ifdef KEEPALIVE
99 const char* free_file_name;
100 FT_Long free_line_no;
101#endif
102
103 FT_MemNode link;
104
105 } FT_MemNodeRec;
106
107
108 /*
109 * The global structure, containing compound statistics and all hash
110 * tables.
111 */
112 typedef struct FT_MemTableRec_
113 {
114 FT_Long size;
115 FT_Long nodes;
116 FT_MemNode* buckets;
117
118 FT_Long alloc_total;
119 FT_Long alloc_current;
120 FT_Long alloc_max;
121 FT_Long alloc_count;
122
123 FT_Bool bound_total;
124 FT_Long alloc_total_max;
125
126 FT_Bool bound_count;
127 FT_Long alloc_count_max;
128
129 FT_MemSource sources[FT_MEM_SOURCE_BUCKETS];
130
131 FT_Bool keep_alive;
132
133 FT_Memory memory;
134 FT_Pointer memory_user;
135 FT_Alloc_Func alloc;
136 FT_Free_Func free;
137 FT_Realloc_Func realloc;
138
139 } FT_MemTableRec;
140
141
142#define FT_MEM_SIZE_MIN 7
143#define FT_MEM_SIZE_MAX 13845163
144
145#define FT_FILENAME( x ) ( (x) ? (x) : "unknown file" )
146
147
148 /*
149 * Prime numbers are ugly to handle. It would be better to implement
150 * L-Hashing, which is 10% faster and doesn't require divisions.
151 */
152 static const FT_Int ft_mem_primes[] =
153 {
154 7,
155 11,
156 19,
157 37,
158 73,
159 109,
160 163,
161 251,
162 367,
163 557,
164 823,
165 1237,
166 1861,
167 2777,
168 4177,
169 6247,
170 9371,
171 14057,
172 21089,
173 31627,
174 47431,
175 71143,
176 106721,
177 160073,
178 240101,
179 360163,
180 540217,
181 810343,
182 1215497,
183 1823231,
184 2734867,
185 4102283,
186 6153409,
187 9230113,
188 13845163,
189 };
190
191
192 static FT_Long
193 ft_mem_closest_prime( FT_Long num )
194 {
195 size_t i;
196
197
198 for ( i = 0;
199 i < sizeof ( ft_mem_primes ) / sizeof ( ft_mem_primes[0] ); i++ )
200 if ( ft_mem_primes[i] > num )
201 return ft_mem_primes[i];
202
203 return FT_MEM_SIZE_MAX;
204 }
205
206
207 static void
208 ft_mem_debug_panic( const char* fmt,
209 ... )
210 {
211 va_list ap;
212
213
214 printf( "FreeType.Debug: " );
215
216 va_start( ap, fmt );
217 vprintf( fmt, ap );
218 va_end( ap );
219
220 printf( "\n" );
221 exit( EXIT_FAILURE );
222 }
223
224
225 static FT_Pointer
226 ft_mem_table_alloc( FT_MemTable table,
227 FT_Long size )
228 {
229 FT_Memory memory = table->memory;
230 FT_Pointer block;
231
232
233 memory->user = table->memory_user;
234 block = table->alloc( memory, size );
235 memory->user = table;
236
237 return block;
238 }
239
240
241 static void
242 ft_mem_table_free( FT_MemTable table,
243 FT_Pointer block )
244 {
245 FT_Memory memory = table->memory;
246
247
248 memory->user = table->memory_user;
249 table->free( memory, block );
250 memory->user = table;
251 }
252
253
254 static void
255 ft_mem_table_resize( FT_MemTable table )
256 {
257 FT_Long new_size;
258
259
260 new_size = ft_mem_closest_prime( table->nodes );
261 if ( new_size != table->size )
262 {
263 FT_MemNode* new_buckets;
264 FT_Long i;
265
266
267 new_buckets = (FT_MemNode *)
268 ft_mem_table_alloc(
269 table,
270 new_size * (FT_Long)sizeof ( FT_MemNode ) );
271 if ( !new_buckets )
272 return;
273
274 FT_ARRAY_ZERO( new_buckets, new_size );
275
276 for ( i = 0; i < table->size; i++ )
277 {
278 FT_MemNode node, next, *pnode;
279 FT_PtrDist hash;
280
281
282 node = table->buckets[i];
283 while ( node )
284 {
285 next = node->link;
286 hash = FT_MEM_VAL( node->address ) % (FT_PtrDist)new_size;
287 pnode = new_buckets + hash;
288
289 node->link = pnode[0];
290 pnode[0] = node;
291
292 node = next;
293 }
294 }
295
296 if ( table->buckets )
297 ft_mem_table_free( table, table->buckets );
298
299 table->buckets = new_buckets;
300 table->size = new_size;
301 }
302 }
303
304
305 static void
306 ft_mem_table_destroy( FT_MemTable table )
307 {
308 FT_Long i;
309 FT_Long leak_count = 0;
310 FT_Long leaks = 0;
311
312
313 /* remove all blocks from the table, revealing leaked ones */
314 for ( i = 0; i < table->size; i++ )
315 {
316 FT_MemNode *pnode = table->buckets + i, next, node = *pnode;
317
318
319 while ( node )
320 {
321 next = node->link;
322 node->link = NULL;
323
324 if ( node->size > 0 )
325 {
326 printf(
327 "leaked memory block at address %p, size %8ld in (%s:%ld)\n",
328 (void*)node->address,
329 node->size,
330 FT_FILENAME( node->source->file_name ),
331 node->source->line_no );
332
333 leak_count++;
334 leaks += node->size;
335
336 ft_mem_table_free( table, node->address );
337 }
338
339 node->address = NULL;
340 node->size = 0;
341
342 ft_mem_table_free( table, node );
343 node = next;
344 }
345 table->buckets[i] = NULL;
346 }
347
348 ft_mem_table_free( table, table->buckets );
349 table->buckets = NULL;
350
351 table->size = 0;
352 table->nodes = 0;
353
354 /* remove all sources */
355 for ( i = 0; i < FT_MEM_SOURCE_BUCKETS; i++ )
356 {
357 FT_MemSource source, next;
358
359
360 for ( source = table->sources[i]; source != NULL; source = next )
361 {
362 next = source->link;
363 ft_mem_table_free( table, source );
364 }
365
366 table->sources[i] = NULL;
367 }
368
369 printf( "FreeType: total memory allocations = %ld\n",
370 table->alloc_total );
371 printf( "FreeType: maximum memory footprint = %ld\n",
372 table->alloc_max );
373
374 if ( leak_count > 0 )
375 ft_mem_debug_panic(
376 "FreeType: %ld bytes of memory leaked in %ld blocks\n",
377 leaks, leak_count );
378
379 printf( "FreeType: no memory leaks detected\n" );
380 }
381
382
383 static FT_MemNode*
384 ft_mem_table_get_nodep( FT_MemTable table,
385 FT_Byte* address )
386 {
387 FT_PtrDist hash;
388 FT_MemNode *pnode, node;
389
390
391 hash = FT_MEM_VAL( address );
392 pnode = table->buckets + ( hash % (FT_PtrDist)table->size );
393
394 for (;;)
395 {
396 node = pnode[0];
397 if ( !node )
398 break;
399
400 if ( node->address == address )
401 break;
402
403 pnode = &node->link;
404 }
405 return pnode;
406 }
407
408
409 static FT_MemSource
410 ft_mem_table_get_source( FT_MemTable table )
411 {
412 FT_UInt32 hash;
413 FT_MemSource node, *pnode;
414
415
416 /* cast to FT_PtrDist first since void* can be larger */
417 /* than FT_UInt32 and GCC 4.1.1 emits a warning */
418 hash = (FT_UInt32)(FT_PtrDist)(void*)ft_debug_file_ +
419 (FT_UInt32)( 5 * ft_debug_lineno_ );
420 pnode = &table->sources[hash % FT_MEM_SOURCE_BUCKETS];
421
422 for (;;)
423 {
424 node = *pnode;
425 if ( !node )
426 break;
427
428 if ( node->file_name == ft_debug_file_ &&
429 node->line_no == ft_debug_lineno_ )
430 goto Exit;
431
432 pnode = &node->link;
433 }
434
435 node = (FT_MemSource)ft_mem_table_alloc( table, sizeof ( *node ) );
436 if ( !node )
437 ft_mem_debug_panic(
438 "not enough memory to perform memory debugging\n" );
439
440 node->file_name = ft_debug_file_;
441 node->line_no = ft_debug_lineno_;
442
443 node->cur_blocks = 0;
444 node->max_blocks = 0;
445 node->all_blocks = 0;
446
447 node->cur_size = 0;
448 node->max_size = 0;
449 node->all_size = 0;
450
451 node->cur_max = 0;
452
453 node->link = NULL;
454 node->hash = hash;
455 *pnode = node;
456
457 Exit:
458 return node;
459 }
460
461
462 static void
463 ft_mem_table_set( FT_MemTable table,
464 FT_Byte* address,
465 FT_Long size,
466 FT_Long delta )
467 {
468 FT_MemNode *pnode, node;
469
470
471 if ( table )
472 {
473 FT_MemSource source;
474
475
476 pnode = ft_mem_table_get_nodep( table, address );
477 node = *pnode;
478 if ( node )
479 {
480 if ( node->size < 0 )
481 {
482 /* This block was already freed. Our memory is now completely */
483 /* corrupted! */
484 /* This can only happen in keep-alive mode. */
485 ft_mem_debug_panic(
486 "memory heap corrupted (allocating freed block)" );
487 }
488 else
489 {
490 /* This block was already allocated. This means that our memory */
491 /* is also corrupted! */
492 ft_mem_debug_panic(
493 "memory heap corrupted (re-allocating allocated block at"
494 " %p, of size %ld)\n"
495 "org=%s:%d new=%s:%d\n",
496 node->address, node->size,
497 FT_FILENAME( node->source->file_name ), node->source->line_no,
498 FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_ );
499 }
500 }
501
502 /* we need to create a new node in this table */
503 node = (FT_MemNode)ft_mem_table_alloc( table, sizeof ( *node ) );
504 if ( !node )
505 ft_mem_debug_panic( "not enough memory to run memory tests" );
506
507 node->address = address;
508 node->size = size;
509 node->source = source = ft_mem_table_get_source( table );
510
511 if ( delta == 0 )
512 {
513 /* this is an allocation */
514 source->all_blocks++;
515 source->cur_blocks++;
516 if ( source->cur_blocks > source->max_blocks )
517 source->max_blocks = source->cur_blocks;
518 }
519
520 if ( size > source->cur_max )
521 source->cur_max = size;
522
523 if ( delta != 0 )
524 {
525 /* we are growing or shrinking a reallocated block */
526 source->cur_size += delta;
527 table->alloc_current += delta;
528 }
529 else
530 {
531 /* we are allocating a new block */
532 source->cur_size += size;
533 table->alloc_current += size;
534 }
535
536 source->all_size += size;
537
538 if ( source->cur_size > source->max_size )
539 source->max_size = source->cur_size;
540
541 node->free_file_name = NULL;
542 node->free_line_no = 0;
543
544 node->link = pnode[0];
545
546 pnode[0] = node;
547 table->nodes++;
548
549 table->alloc_total += size;
550
551 if ( table->alloc_current > table->alloc_max )
552 table->alloc_max = table->alloc_current;
553
554 if ( table->nodes * 3 < table->size ||
555 table->size * 3 < table->nodes )
556 ft_mem_table_resize( table );
557 }
558 }
559
560
561 static void
562 ft_mem_table_remove( FT_MemTable table,
563 FT_Byte* address,
564 FT_Long delta )
565 {
566 if ( table )
567 {
568 FT_MemNode *pnode, node;
569
570
571 pnode = ft_mem_table_get_nodep( table, address );
572 node = *pnode;
573 if ( node )
574 {
575 FT_MemSource source;
576
577
578 if ( node->size < 0 )
579 ft_mem_debug_panic(
580 "freeing memory block at %p more than once\n"
581 " at (%s:%ld)!\n"
582 " Block was allocated at (%s:%ld)\n"
583 " and released at (%s:%ld).",
584 address,
585 FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_,
586 FT_FILENAME( node->source->file_name ), node->source->line_no,
587 FT_FILENAME( node->free_file_name ), node->free_line_no );
588
589 /* scramble the node's content for additional safety */
590 FT_MEM_SET( address, 0xF3, node->size );
591
592 if ( delta == 0 )
593 {
594 source = node->source;
595
596 source->cur_blocks--;
597 source->cur_size -= node->size;
598
599 table->alloc_current -= node->size;
600 }
601
602 if ( table->keep_alive )
603 {
604 /* we simply invert the node's size to indicate that the node */
605 /* was freed. */
606 node->size = -node->size;
607 node->free_file_name = ft_debug_file_;
608 node->free_line_no = ft_debug_lineno_;
609 }
610 else
611 {
612 table->nodes--;
613
614 *pnode = node->link;
615
616 node->size = 0;
617 node->source = NULL;
618
619 ft_mem_table_free( table, node );
620
621 if ( table->nodes * 3 < table->size ||
622 table->size * 3 < table->nodes )
623 ft_mem_table_resize( table );
624 }
625 }
626 else
627 ft_mem_debug_panic(
628 "trying to free unknown block at %p in (%s:%ld)\n",
629 address,
630 FT_FILENAME( ft_debug_file_ ), ft_debug_lineno_ );
631 }
632 }
633
634
635 static FT_Pointer
636 ft_mem_debug_alloc( FT_Memory memory,
637 FT_Long size )
638 {
639 FT_MemTable table = (FT_MemTable)memory->user;
640 FT_Byte* block;
641
642
643 if ( size <= 0 )
644 ft_mem_debug_panic( "negative block size allocation (%ld)", size );
645
646 /* return NULL if the maximum number of allocations was reached */
647 if ( table->bound_count &&
648 table->alloc_count >= table->alloc_count_max )
649 return NULL;
650
651 /* return NULL if this allocation would overflow the maximum heap size */
652 if ( table->bound_total &&
653 table->alloc_total_max - table->alloc_current > size )
654 return NULL;
655
656 block = (FT_Byte *)ft_mem_table_alloc( table, size );
657 if ( block )
658 {
659 ft_mem_table_set( table, block, size, 0 );
660
661 table->alloc_count++;
662 }
663
664 ft_debug_file_ = "<unknown>";
665 ft_debug_lineno_ = 0;
666
667 return (FT_Pointer)block;
668 }
669
670
671 static void
672 ft_mem_debug_free( FT_Memory memory,
673 FT_Pointer block )
674 {
675 FT_MemTable table = (FT_MemTable)memory->user;
676
677
678 if ( !block )
679 ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",
680 FT_FILENAME( ft_debug_file_ ),
681 ft_debug_lineno_ );
682
683 ft_mem_table_remove( table, (FT_Byte*)block, 0 );
684
685 if ( !table->keep_alive )
686 ft_mem_table_free( table, block );
687
688 table->alloc_count--;
689
690 ft_debug_file_ = "<unknown>";
691 ft_debug_lineno_ = 0;
692 }
693
694
695 static FT_Pointer
696 ft_mem_debug_realloc( FT_Memory memory,
697 FT_Long cur_size,
698 FT_Long new_size,
699 FT_Pointer block )
700 {
701 FT_MemTable table = (FT_MemTable)memory->user;
702 FT_MemNode node, *pnode;
703 FT_Pointer new_block;
704 FT_Long delta;
705
706 const char* file_name = FT_FILENAME( ft_debug_file_ );
707 FT_Long line_no = ft_debug_lineno_;
708
709
710 /* unlikely, but possible */
711 if ( new_size == cur_size )
712 return block;
713
714 /* the following is valid according to ANSI C */
715#if 0
716 if ( !block || !cur_size )
717 ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",
718 file_name, line_no );
719#endif
720
721 /* while the following is allowed in ANSI C also, we abort since */
722 /* such case should be handled by FreeType. */
723 if ( new_size <= 0 )
724 ft_mem_debug_panic(
725 "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",
726 block, cur_size, file_name, line_no );
727
728 /* check `cur_size' value */
729 pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );
730 node = *pnode;
731 if ( !node )
732 ft_mem_debug_panic(
733 "trying to reallocate unknown block at %p in (%s:%ld)",
734 block, file_name, line_no );
735
736 if ( node->size <= 0 )
737 ft_mem_debug_panic(
738 "trying to reallocate freed block at %p in (%s:%ld)",
739 block, file_name, line_no );
740
741 if ( node->size != cur_size )
742 ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is "
743 "%ld instead of %ld in (%s:%ld)",
744 block, cur_size, node->size, file_name, line_no );
745
746 /* return NULL if the maximum number of allocations was reached */
747 if ( table->bound_count &&
748 table->alloc_count >= table->alloc_count_max )
749 return NULL;
750
751 delta = new_size - cur_size;
752
753 /* return NULL if this allocation would overflow the maximum heap size */
754 if ( delta > 0 &&
755 table->bound_total &&
756 table->alloc_current + delta > table->alloc_total_max )
757 return NULL;
758
759 new_block = (FT_Pointer)ft_mem_table_alloc( table, new_size );
760 if ( !new_block )
761 return NULL;
762
763 ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );
764
765 ft_memcpy( new_block, block, cur_size < new_size ? (size_t)cur_size
766 : (size_t)new_size );
767
768 ft_mem_table_remove( table, (FT_Byte*)block, delta );
769
770 ft_debug_file_ = "<unknown>";
771 ft_debug_lineno_ = 0;
772
773 if ( !table->keep_alive )
774 ft_mem_table_free( table, block );
775
776 return new_block;
777 }
778
779
780 extern void
781 ft_mem_debug_init( FT_Memory memory )
782 {
783 FT_MemTable table;
784
785
786 if ( !ft_getenv( "FT2_DEBUG_MEMORY" ) )
787 return;
788
789 table = (FT_MemTable)memory->alloc( memory, sizeof ( *table ) );
790
791 if ( table )
792 {
793 FT_ZERO( table );
794
795 table->memory = memory;
796 table->memory_user = memory->user;
797 table->alloc = memory->alloc;
798 table->realloc = memory->realloc;
799 table->free = memory->free;
800
801 ft_mem_table_resize( table );
802
803 if ( table->size )
804 {
805 const char* p;
806
807
808 memory->user = table;
809 memory->alloc = ft_mem_debug_alloc;
810 memory->realloc = ft_mem_debug_realloc;
811 memory->free = ft_mem_debug_free;
812
813 p = ft_getenv( "FT2_ALLOC_TOTAL_MAX" );
814 if ( p )
815 {
816 FT_Long total_max = ft_strtol( p, NULL, 10 );
817
818
819 if ( total_max > 0 )
820 {
821 table->bound_total = 1;
822 table->alloc_total_max = total_max;
823 }
824 }
825
826 p = ft_getenv( "FT2_ALLOC_COUNT_MAX" );
827 if ( p )
828 {
829 FT_Long total_count = ft_strtol( p, NULL, 10 );
830
831
832 if ( total_count > 0 )
833 {
834 table->bound_count = 1;
835 table->alloc_count_max = total_count;
836 }
837 }
838
839 p = ft_getenv( "FT2_KEEP_ALIVE" );
840 if ( p )
841 {
842 FT_Long keep_alive = ft_strtol( p, NULL, 10 );
843
844
845 if ( keep_alive > 0 )
846 table->keep_alive = 1;
847 }
848 }
849 else
850 memory->free( memory, table );
851 }
852 }
853
854
855 extern void
856 ft_mem_debug_done( FT_Memory memory )
857 {
858 if ( memory->free == ft_mem_debug_free )
859 {
860 FT_MemTable table = (FT_MemTable)memory->user;
861
862
863 FT_DumpMemory( memory );
864
865 ft_mem_table_destroy( table );
866
867 memory->free = table->free;
868 memory->realloc = table->realloc;
869 memory->alloc = table->alloc;
870 memory->user = table->memory_user;
871
872 memory->free( memory, table );
873 }
874 }
875
876
877 FT_COMPARE_DEF( int )
878 ft_mem_source_compare( const void* p1,
879 const void* p2 )
880 {
881 FT_MemSource s1 = *(FT_MemSource*)p1;
882 FT_MemSource s2 = *(FT_MemSource*)p2;
883
884
885 if ( s2->max_size > s1->max_size )
886 return 1;
887 else if ( s2->max_size < s1->max_size )
888 return -1;
889 else
890 return 0;
891 }
892
893
894 extern void
895 FT_DumpMemory( FT_Memory memory )
896 {
897 if ( memory->free == ft_mem_debug_free )
898 {
899 FT_MemTable table = (FT_MemTable)memory->user;
900 FT_MemSource* bucket = table->sources;
901 FT_MemSource* limit = bucket + FT_MEM_SOURCE_BUCKETS;
902 FT_MemSource* sources;
903 FT_Int nn, count;
904 const char* fmt;
905
906
907 count = 0;
908 for ( ; bucket < limit; bucket++ )
909 {
910 FT_MemSource source = *bucket;
911
912
913 for ( ; source; source = source->link )
914 count++;
915 }
916
917 sources = (FT_MemSource*)
918 ft_mem_table_alloc(
919 table, count * (FT_Long)sizeof ( *sources ) );
920
921 count = 0;
922 for ( bucket = table->sources; bucket < limit; bucket++ )
923 {
924 FT_MemSource source = *bucket;
925
926
927 for ( ; source; source = source->link )
928 sources[count++] = source;
929 }
930
931 ft_qsort( sources,
932 (size_t)count,
933 sizeof ( *sources ),
934 ft_mem_source_compare );
935
936 printf( "FreeType Memory Dump: "
937 "current=%ld max=%ld total=%ld count=%ld\n",
938 table->alloc_current, table->alloc_max,
939 table->alloc_total, table->alloc_count );
940 printf( " block block sizes sizes sizes source\n" );
941 printf( " count high sum highsum max location\n" );
942 printf( "-------------------------------------------------\n" );
943
944 fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";
945
946 for ( nn = 0; nn < count; nn++ )
947 {
948 FT_MemSource source = sources[nn];
949
950
951 printf( fmt,
952 source->cur_blocks, source->max_blocks,
953 source->cur_size, source->max_size, source->cur_max,
954 FT_FILENAME( source->file_name ),
955 source->line_no );
956 }
957 printf( "------------------------------------------------\n" );
958
959 ft_mem_table_free( table, sources );
960 }
961 }
962
963#else /* !FT_DEBUG_MEMORY */
964
965 /* ANSI C doesn't like empty source files */
966 typedef int debug_mem_dummy_;
967
968#endif /* !FT_DEBUG_MEMORY */
969
970
971/* END */
972