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 | |