1 /****************************************************************************
   2  *
   3  * ftdbgmem.c
   4  *
   5  *   Memory debugger (body).
   6  *
   7  * Copyright (C) 2001-2020 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\n"
 625             "  at (%s:%ld)!\n"
 626             "  Block was allocated at (%s:%ld)\n"
 627             "  and released at (%s:%ld).",
 628             address,
 629             FT_FILENAME( _ft_debug_file ), _ft_debug_lineno,
 630             FT_FILENAME( node->source->file_name ), node->source->line_no,
 631             FT_FILENAME( node->free_file_name ), node->free_line_no );
 632 
 633         /* scramble the node's content for additional safety */
 634         FT_MEM_SET( address, 0xF3, node->size );
 635 
 636         if ( delta == 0 )
 637         {
 638           source = node->source;
 639 
 640           source->cur_blocks--;
 641           source->cur_size -= node->size;
 642 
 643           table->alloc_current -= node->size;
 644         }
 645 
 646         if ( table->keep_alive )
 647         {
 648           /* we simply invert the node's size to indicate that the node */
 649           /* was freed.                                                 */
 650           node->size           = -node->size;
 651           node->free_file_name = _ft_debug_file;
 652           node->free_line_no   = _ft_debug_lineno;
 653         }
 654         else
 655         {
 656           table->nodes--;
 657 
 658           *pnode = node->link;
 659 
 660           node->size   = 0;
 661           node->source = NULL;
 662 
 663           ft_mem_table_free( table, node );
 664 
 665           if ( table->nodes * 3 < table->size  ||
 666                table->size  * 3 < table->nodes )
 667             ft_mem_table_resize( table );
 668         }
 669       }
 670       else
 671         ft_mem_debug_panic(
 672           "trying to free unknown block at %p in (%s:%ld)\n",
 673           address,
 674           FT_FILENAME( _ft_debug_file ), _ft_debug_lineno );
 675     }
 676   }
 677 
 678 
 679   static FT_Pointer
 680   ft_mem_debug_alloc( FT_Memory  memory,
 681                       FT_Long    size )
 682   {
 683     FT_MemTable  table = (FT_MemTable)memory->user;
 684     FT_Byte*     block;
 685 
 686 
 687     if ( size <= 0 )
 688       ft_mem_debug_panic( "negative block size allocation (%ld)", size );
 689 
 690     /* return NULL if the maximum number of allocations was reached */
 691     if ( table->bound_count                           &&
 692          table->alloc_count >= table->alloc_count_max )
 693       return NULL;
 694 
 695     /* return NULL if this allocation would overflow the maximum heap size */
 696     if ( table->bound_total                                   &&
 697          table->alloc_total_max - table->alloc_current > size )
 698       return NULL;
 699 
 700     block = (FT_Byte *)ft_mem_table_alloc( table, size );
 701     if ( block )
 702     {
 703       ft_mem_table_set( table, block, size, 0 );
 704 
 705       table->alloc_count++;
 706     }
 707 
 708     _ft_debug_file   = "<unknown>";
 709     _ft_debug_lineno = 0;
 710 
 711     return (FT_Pointer)block;
 712   }
 713 
 714 
 715   static void
 716   ft_mem_debug_free( FT_Memory   memory,
 717                      FT_Pointer  block )
 718   {
 719     FT_MemTable  table = (FT_MemTable)memory->user;
 720 
 721 
 722     if ( !block )
 723       ft_mem_debug_panic( "trying to free NULL in (%s:%ld)",
 724                           FT_FILENAME( _ft_debug_file ),
 725                           _ft_debug_lineno );
 726 
 727     ft_mem_table_remove( table, (FT_Byte*)block, 0 );
 728 
 729     if ( !table->keep_alive )
 730       ft_mem_table_free( table, block );
 731 
 732     table->alloc_count--;
 733 
 734     _ft_debug_file   = "<unknown>";
 735     _ft_debug_lineno = 0;
 736   }
 737 
 738 
 739   static FT_Pointer
 740   ft_mem_debug_realloc( FT_Memory   memory,
 741                         FT_Long     cur_size,
 742                         FT_Long     new_size,
 743                         FT_Pointer  block )
 744   {
 745     FT_MemTable  table = (FT_MemTable)memory->user;
 746     FT_MemNode   node, *pnode;
 747     FT_Pointer   new_block;
 748     FT_Long      delta;
 749 
 750     const char*  file_name = FT_FILENAME( _ft_debug_file );
 751     FT_Long      line_no   = _ft_debug_lineno;
 752 
 753 
 754     /* unlikely, but possible */
 755     if ( new_size == cur_size )
 756       return block;
 757 
 758     /* the following is valid according to ANSI C */
 759 #if 0
 760     if ( !block || !cur_size )
 761       ft_mem_debug_panic( "trying to reallocate NULL in (%s:%ld)",
 762                           file_name, line_no );
 763 #endif
 764 
 765     /* while the following is allowed in ANSI C also, we abort since */
 766     /* such case should be handled by FreeType.                      */
 767     if ( new_size <= 0 )
 768       ft_mem_debug_panic(
 769         "trying to reallocate %p to size 0 (current is %ld) in (%s:%ld)",
 770         block, cur_size, file_name, line_no );
 771 
 772     /* check `cur_size' value */
 773     pnode = ft_mem_table_get_nodep( table, (FT_Byte*)block );
 774     node  = *pnode;
 775     if ( !node )
 776       ft_mem_debug_panic(
 777         "trying to reallocate unknown block at %p in (%s:%ld)",
 778         block, file_name, line_no );
 779 
 780     if ( node->size <= 0 )
 781       ft_mem_debug_panic(
 782         "trying to reallocate freed block at %p in (%s:%ld)",
 783         block, file_name, line_no );
 784 
 785     if ( node->size != cur_size )
 786       ft_mem_debug_panic( "invalid ft_realloc request for %p. cur_size is "
 787                           "%ld instead of %ld in (%s:%ld)",
 788                           block, cur_size, node->size, file_name, line_no );
 789 
 790     /* return NULL if the maximum number of allocations was reached */
 791     if ( table->bound_count                           &&
 792          table->alloc_count >= table->alloc_count_max )
 793       return NULL;
 794 
 795     delta = new_size - cur_size;
 796 
 797     /* return NULL if this allocation would overflow the maximum heap size */
 798     if ( delta > 0                                             &&
 799          table->bound_total                                    &&
 800          table->alloc_current + delta > table->alloc_total_max )
 801       return NULL;
 802 
 803     new_block = (FT_Pointer)ft_mem_table_alloc( table, new_size );
 804     if ( !new_block )
 805       return NULL;
 806 
 807     ft_mem_table_set( table, (FT_Byte*)new_block, new_size, delta );
 808 
 809     ft_memcpy( new_block, block, cur_size < new_size ? (size_t)cur_size
 810                                                      : (size_t)new_size );
 811 
 812     ft_mem_table_remove( table, (FT_Byte*)block, delta );
 813 
 814     _ft_debug_file   = "<unknown>";
 815     _ft_debug_lineno = 0;
 816 
 817     if ( !table->keep_alive )
 818       ft_mem_table_free( table, block );
 819 
 820     return new_block;
 821   }
 822 
 823 
 824   extern FT_Int
 825   ft_mem_debug_init( FT_Memory  memory )
 826   {
 827     FT_MemTable  table;
 828     FT_Int       result = 0;
 829 
 830 
 831     if ( ft_getenv( "FT2_DEBUG_MEMORY" ) )
 832     {
 833       table = ft_mem_table_new( memory );
 834       if ( table )
 835       {
 836         const char*  p;
 837 
 838 
 839         memory->user    = table;
 840         memory->alloc   = ft_mem_debug_alloc;
 841         memory->realloc = ft_mem_debug_realloc;
 842         memory->free    = ft_mem_debug_free;
 843 
 844         p = ft_getenv( "FT2_ALLOC_TOTAL_MAX" );
 845         if ( p )
 846         {
 847           FT_Long  total_max = ft_strtol( p, NULL, 10 );
 848 
 849 
 850           if ( total_max > 0 )
 851           {
 852             table->bound_total     = 1;
 853             table->alloc_total_max = total_max;
 854           }
 855         }
 856 
 857         p = ft_getenv( "FT2_ALLOC_COUNT_MAX" );
 858         if ( p )
 859         {
 860           FT_Long  total_count = ft_strtol( p, NULL, 10 );
 861 
 862 
 863           if ( total_count > 0 )
 864           {
 865             table->bound_count     = 1;
 866             table->alloc_count_max = total_count;
 867           }
 868         }
 869 
 870         p = ft_getenv( "FT2_KEEP_ALIVE" );
 871         if ( p )
 872         {
 873           FT_Long  keep_alive = ft_strtol( p, NULL, 10 );
 874 
 875 
 876           if ( keep_alive > 0 )
 877             table->keep_alive = 1;
 878         }
 879 
 880         result = 1;
 881       }
 882     }
 883     return result;
 884   }
 885 
 886 
 887   extern void
 888   ft_mem_debug_done( FT_Memory  memory )
 889   {
 890     FT_MemTable  table = (FT_MemTable)memory->user;
 891 
 892 
 893     if ( table )
 894     {
 895       memory->free    = table->free;
 896       memory->realloc = table->realloc;
 897       memory->alloc   = table->alloc;
 898 
 899       ft_mem_table_destroy( table );
 900       memory->user = NULL;
 901     }
 902   }
 903 
 904 
 905   static int
 906   ft_mem_source_compare( const void*  p1,
 907                          const void*  p2 )
 908   {
 909     FT_MemSource  s1 = *(FT_MemSource*)p1;
 910     FT_MemSource  s2 = *(FT_MemSource*)p2;
 911 
 912 
 913     if ( s2->max_size > s1->max_size )
 914       return 1;
 915     else if ( s2->max_size < s1->max_size )
 916       return -1;
 917     else
 918       return 0;
 919   }
 920 
 921 
 922   extern void
 923   FT_DumpMemory( FT_Memory  memory )
 924   {
 925     FT_MemTable  table = (FT_MemTable)memory->user;
 926 
 927 
 928     if ( table )
 929     {
 930       FT_MemSource*  bucket = table->sources;
 931       FT_MemSource*  limit  = bucket + FT_MEM_SOURCE_BUCKETS;
 932       FT_MemSource*  sources;
 933       FT_Int         nn, count;
 934       const char*    fmt;
 935 
 936 
 937       count = 0;
 938       for ( ; bucket < limit; bucket++ )
 939       {
 940         FT_MemSource  source = *bucket;
 941 
 942 
 943         for ( ; source; source = source->link )
 944           count++;
 945       }
 946 
 947       sources = (FT_MemSource*)
 948                   ft_mem_table_alloc(
 949                     table, count * (FT_Long)sizeof ( *sources ) );
 950 
 951       count = 0;
 952       for ( bucket = table->sources; bucket < limit; bucket++ )
 953       {
 954         FT_MemSource  source = *bucket;
 955 
 956 
 957         for ( ; source; source = source->link )
 958           sources[count++] = source;
 959       }
 960 
 961       ft_qsort( sources,
 962                 (size_t)count,
 963                 sizeof ( *sources ),
 964                 ft_mem_source_compare );
 965 
 966       printf( "FreeType Memory Dump: "
 967               "current=%ld max=%ld total=%ld count=%ld\n",
 968               table->alloc_current, table->alloc_max,
 969               table->alloc_total, table->alloc_count );
 970       printf( " block  block    sizes    sizes    sizes   source\n" );
 971       printf( " count   high      sum  highsum      max   location\n" );
 972       printf( "-------------------------------------------------\n" );
 973 
 974       fmt = "%6ld %6ld %8ld %8ld %8ld %s:%d\n";
 975 
 976       for ( nn = 0; nn < count; nn++ )
 977       {
 978         FT_MemSource  source = sources[nn];
 979 
 980 
 981         printf( fmt,
 982                 source->cur_blocks, source->max_blocks,
 983                 source->cur_size, source->max_size, source->cur_max,
 984                 FT_FILENAME( source->file_name ),
 985                 source->line_no );
 986       }
 987       printf( "------------------------------------------------\n" );
 988 
 989       ft_mem_table_free( table, sources );
 990     }
 991   }
 992 
 993 #else  /* !FT_DEBUG_MEMORY */
 994 
 995   /* ANSI C doesn't like empty source files */
 996   typedef int  _debug_mem_dummy;
 997 
 998 #endif /* !FT_DEBUG_MEMORY */
 999 
1000 
1001 /* END */