(root)/
freetype-2.13.2/
src/
base/
ftdbgmem.c
       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 */