1 | /**************************************************************************** |
2 | * |
3 | * ftdbgmem.c |
4 | * |
5 | * Memory debugger (body). |
6 | * |
7 | * Copyright (C) 2001-2019 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 ( ft_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 = ft_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 = ft_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 = ft_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 | |