/** @file vmalloc.C
    @brief Validating malloc routines
    @author Patrick Audley <paudley@blackcat.ca>
    @license_gpl
*/
/*
    Copyright 1995-2003 Patrick C. Audley, Geoff J. Barton, Robert B. Russel

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <string.h>
#include <stdarg.h>
#include <map>
#include <vector>
#include <string>
#include "vmalloc.h"

#ifdef VMALLOC_ALWAYS
#error "You can't use Vmalloc on Vmalloc... bad things will happen."
#endif

unsigned int Vmalloc_verbose = 0;

//! Bookkeeping structure for _Vmalloc() and _Vfree()
struct mem_region {
  void* ptr;          //!< address of our region
  unsigned long size; //!< size requested
  char file[64];      //!< file name of the caller
  char note[128];     //!< note for this region
  long line;          //!< line number of the caller
  long pass;          //!< This is the n-th allocation from this location
  bool freed;         //!< Whether we've been free'd yet
  bool realloced;     //!< Whether we've been realloc'ed (and have info in Vmem_reallocs)
  void* replaced;     //!<  If we've been realloc'ed and realloc replaced us, what did it replace us with?
};

/** Null mem_region for use in _Vmemget()
    @internal  */
mem_region vmem_null;

//! Bookeeping map of allocations for _Vmalloc() and _Vfree()
typedef std::map<void*,mem_region> Vmem_acct_t;

//! Vector of allocations for _Vrealloc()
typedef std::vector<mem_region> Vmem_realloc_t;

//! Our map for use in the functions below.
Vmem_acct_t Vmem_acct;

//! Our map of previous frees
Vmem_acct_t Vmem_frees;

//! map of hashed file and line positions and number allocations seen from there
std::map<unsigned long,unsigned long> Vmem_seen;

//! map of vectors of reallocation information
std::map<void *,Vmem_realloc_t> Vmem_reallocs;

//! if we are validating then we need to setup account once.  This is whether we have or not.
static bool Vmem_accounting_setup = false;

//! Check bytes for head of allocations
const long Vchk_head = 0xABBACACC;
//! Check bytes for tail of allocations
const long Vchk_tail = 0xDEADBEEF;
//! Check bytes for head of reallocations
const long Vchk_realloc = 0xBEA110CA;

//! should we process the accounting lists?
static bool Vchk_accounting = true;

/** Whether we've built the crc_table or not. */
static bool crc_init = false;
/** CRC32 table */
static unsigned long crc_table[256];
/** Build the crc_table */
static void gen_table(void) {
  unsigned long crc, poly = 0xEDB88320L; for (long i = 0; i < 256; i++) { crc = i; for (long j = 8; j > 0; j--) { if (crc & 1) crc = (crc >> 1) ^ poly; else crc >>= 1; } crc_table[i] = crc; } 
}

/** @brief calculate crc32 of a memory region.
    @param buf pointer to memory to hash
    @param len size of memory to hash
    @return the crc32 of the memory region
    Used to index the Vmem_seen map.
*/
static unsigned long get_crc( char* buf, size_t len ) {
    register unsigned long crc = 0xFFFFFFFF;
    if( !crc_init ) gen_table();
    for( long i = 0; i < len; i++ )
      crc = ((crc>>8) & 0x00FFFFFF) ^ crc_table[ (crc^buf[i]) & 0xFF ];
    return( crc^0xFFFFFFFF );
}

//! buffer for Vmemprint()
static char *memprint_buf;

//! size to allocate for memprint_buf
#define MEMPRINT_BUFSIZE 8192

/** given a mem_rgion, return a string suitable for printing 
    @param M region to display
    @return a pointer to memprint_buf
    @internal
*/
static char *Vmemprint( mem_region &M ) {
  snprintf( memprint_buf, MEMPRINT_BUFSIZE, "%s:%ld %s%s%spass %lu [%ld bytes]",
	    M.file, M.line,
	    M.note[0] != 0x00 ? "(" : "",
	    M.note[0] != 0x00 ? M.note : "",
	    M.note[0] != 0x00 ? ") " :  "",
	    M.pass,
	    M.size
	    );
  if( M.replaced ) {
    Vmem_realloc_t &V = Vmem_reallocs[ M.ptr ];
    unsigned long last_size = M.size;
    char* temp_buf = (char*)malloc( MEMPRINT_BUFSIZE );
    if( !temp_buf ) {
      fprintf( stderr, "internal malloc failed in Vmemprint!\n" );
      exit(EXIT_FAILURE);
    }
    for( Vmem_realloc_t::iterator i = V.begin();
	 i != V.end();
	 i++ ) {
      snprintf( temp_buf, MEMPRINT_BUFSIZE, "%s\n -> realloced at %s:%d %s%s%sfrom %ld to %ld bytes, old address was %p", 
		memprint_buf, (*i).file, (*i).line, 
		(*i).note[0] != 0x00 ? "(" : "",
		(*i).note[0] != 0x00 ? (*i).note : "",
		(*i).note[0] != 0x00 ? ") " :  "",
		(*i).size, M.size, (*i).replaced );
      strncpy( memprint_buf, temp_buf, MEMPRINT_BUFSIZE );
    }
    free( temp_buf );
  }
  return memprint_buf;
}

/** fetch refernce to mem_region for ptr
    @param ptr pointer to region to search for
    @param func string of caller's name
    @param file string of caller's caller's file
    @param line number of caller's caller's line
    @param peek if true then exit the program if we don't find the region
    @return mem_region reference
    @internal
*/
static mem_region &Vmemget( void* ptr, const char* func, const char* file, const long line, bool peek = false ) {
  if( Vmem_acct.find( ptr ) == Vmem_acct.end() ) {
    if( peek ) {
      memset( &vmem_null, 0x00, sizeof(mem_region) );
      return vmem_null;
    }
    // This means we don't have a record of this allocation...
    fprintf( stderr, "%s called at %s:%ld to free a chunk of memory at %p that wasn't allocated with Vmalloc!\n",
	     func, file, line, ptr );
    VhexdumpPoint( (char*)ptr-16, 32, ptr );
    Vchk_accounting = false;
    exit(EXIT_FAILURE);
  }
  return Vmem_acct[ ptr ];
}

/** given a point, return a strdup'ed suitable for printing.
    @param ptr region to print information about
    @param file string of caller's caller's file
    @param line number of caller's caller's line
    @return strdup'ed string containing information suitable for printing or @c NULL if the region isn't managed by Vmalloc.

    @note
    This function calles Vstrdup() internally and it's return value must be Vfree()'d
*/
char *_Vmemprint( void* ptr, const char* file, const long line ) {
  mem_region M = Vmemget( ptr, "_Vmemprint", file, line, true ); // Just peek, don't abort.
  if( strlen( M.file ) == 0 ) // Region wasn't allocated via Vmalloc()
    return NULL;
  return _Vstrdup( Vmemprint( M ), file, line );
}


/** @brief atexit hook function to check the blc memory accounting if we are validating.

  This function is registered with atexit() and called on program exit.
*/
void Vmem_accounting_check( void ) {
  if( !Vchk_accounting ) return;
  if( Vmalloc_verbose ) printf( "checking memory...\n" );
  // Walk the account map and find any memory that wasn't freed.
  for( Vmem_acct_t::iterator i = Vmem_acct.begin();
       i != Vmem_acct.end();
       i++ )
    {
      if( !i->second.freed && !i->second.realloced ) {
	fprintf( stderr, "found unfreed allocation from %s at %p\n",
		 Vmemprint( i->second ), i->first );
      }
    }
  free( memprint_buf );
}

/** @brief internal strdup routing
    @param str string to copy
    @param file we are called from
    @param line we are called from
    @return malloc'ed pointer to shiny new memory containing your string
    @sa Vstrdup()
    @attention
    This routine should be called via the Vstrdup() macro only.

    This strdup wrapper uses _Vmalloc() if we are in validating mode.
*/
char* _Vstrdup( const char* str, const char* file, const long line ) {
  // If we are validating then use _Vmalloc() instead of calling strdup.
  char* ret = (char*)_Vmalloc( strlen( str ) + 1, file, line );
  strcpy( ret, str );
  return ret;
}

/** @brief internal realloc routine
    @param ptr region of memory to resize
    @param size new size of the region
    @param file we are called from
    @param line we are called from
    @return malloc'ed pointer to shiny new resized memory
    @sa _Vmalloc() Vrealloc()

    @attention
    This routine should be called via the Vrealloc() macro only.

    @note 
    Passing zero or less as @c size will result in the memory being freed.

    This realloc wrapper changes the size of memory that has been
    allocated previously by malloc.  Reallocs are tracked for a given
    region and can be displayed via Vmemprint.
*/
void* _Vrealloc( void * ptr, size_t size, const char* file, const long line ) {
  // verify the args.
  if( size < 1 ) {
    if( Vmalloc_verbose ) printf( "Vrealloc called with a zero size, calling Vfree from %s:%ld\n", file, line );
    _Vfree( ptr, file, line ); // Emulate realloc(ptr,0) behaviour of calling free
    return NULL;
  }
  

  // Check the region
  _Vmemcheck( ptr, file, line, "checking during realloc" );

  // Look up the existing region
  mem_region &M = Vmemget( ptr, "Vfree", file, line );
  void *orig_ptr = (char*)ptr - sizeof(long);
  unsigned long orig_size = M.size;

  
  // Ignore request to change size to the current size
  if( size == M.size ) {
    if( Vmalloc_verbose ) printf( "Vrealloc called with size == current region size of %ld, doing nothing on %s:%ld\n", size, file, line );
    return ptr;
  }

  /*  Ok..  this is sneaky.  Here we add a new checkbyte at the beginning.  Before realloc it should look like:
   *      0xABBACACC ....  0xDEADBEEF
   *  After Vrealloc it will be:
   *      0xBEA110CA 0xABBACACC ... 0xDEADBEEF
   *  This allows us to detect attempt the following error:
   *    void* ptr = malloc( 10 );
   *    realloc( ptr, 20 );
   *    free( ptr );
   *  realloc(3) reserves the right to move the memory to somewhere else and return a ptr that different then it's
   *  argument.  Programmers should update use:
   *    ptr = realloc(ptr,size)
   *  and we catch this even when the memory region isn't moved by realloc.
   */
  void *new_ptr = realloc( orig_ptr, size + sizeof(long)*3 );
  if( !new_ptr ) {
    fprintf( stderr, "Can't re-allocate memory on %s:%ld.\n", file, line );
    Vchk_accounting = false;
    exit(EXIT_FAILURE);
  }

  // Move the memory up one byte
  memmove( (long*)new_ptr+1, (long*)new_ptr, M.size+sizeof(long)*2 );

  // Install the realloc check byte
  memcpy( (char*)new_ptr, &Vchk_realloc, sizeof(long) );

  // clear the new memory
  if( size > M.size )
    memset( (char*)new_ptr + M.size + sizeof(long)*2, 0x00, size - M.size + sizeof(long));

  // Update the tail bytes
  memcpy( (char*)new_ptr + size + sizeof(long)*2, &Vchk_tail, sizeof(long) );
  
  // Fudge new_ptr to skip the head bytes
  new_ptr = (char*)new_ptr + sizeof(long)*2;

  void* rep_ptr = NULL;
  
  // update the accounting
  mem_region R;
  mem_region N = M;
  memset( &R, 0x00, sizeof(mem_region));
  R.size = N.size;
  R.line = line;
  strncpy( R.file, file, 64 );
  if( strlen( N.note ) == 0 ) 
    snprintf( R.note, 128, "reallocated at %s:%ld from %ld to %ld bytes, old ptr was %p, new one is %p", file, line, M.size, R.size, ptr, new_ptr  );
  else 
    strncpy( R.note, N.note, 128 );
  if( N.replaced ) 
    R.replaced = N.replaced;
  else 
    R.replaced = orig_ptr;
  // Change the mem_region.
  N.size = size;
  R.realloced = true;
  N.realloced = false;
  N.replaced = orig_ptr;

  if( orig_ptr == new_ptr ) 
    // We didn't get a new pointer.
    // Copy the old record, remove it, add it back with the new ptr
    Vmem_acct.erase( Vmem_acct.find( orig_ptr ) );

  // Store the reallocation
  Vmem_reallocs[ R.replaced ].push_back( R );

  // Store the new region
  Vmem_acct[ new_ptr ] = N;

  M.size = size;
  M.realloced = true;
  
  if( Vmalloc_verbose ) printf( "Vmalloc reallocated %p from %lu to %lu bytes for %s:%ld\n",

				new_ptr, orig_size, size, file, line );

  // replace the tail check bytes
  return new_ptr;
}

/** @brief internal malloc routine
    @param size number of bytes to allocate
    @param file we are called from
    @param line we are called from
    @return malloc'ed pointer to shiny new memory
    @sa _Vfree() Vmalloc()

    @attention
    This routine should be called via the Vmalloc() macro only.

    This malloc wrapper clears it's memory afterward, checks for
    errors, and does extra validation if we are in validating mode.
*/
void* _Vmalloc( size_t size, const char* file, const long line ) {
  if( Vmem_accounting_setup == false ) {
    // setup accounting
    memprint_buf = (char*)malloc(MEMPRINT_BUFSIZE);
    atexit( &Vmem_accounting_check );
    Vmem_accounting_setup = true;
  }

  // Check for invalid sizes
  if( size < 1 ) {
    fprintf( stderr, "Invalid size in memory allocation on %s:%ld.\n", file, line );
    Vchk_accounting = false;
    exit(EXIT_FAILURE);
  }    

  // Add room for the head+tail bytes and called malloc.
  void* ptr = malloc( size + sizeof(long)*2 );

  if( !ptr ) {
    fprintf( stderr, "Can't allocate memory on %s:%ld.\n", file, line );
    Vchk_accounting = false;
    exit(EXIT_FAILURE);
  }

  // Clear the memory
  memset( ptr, 0x00, size + sizeof(long)*2 );

  // Fudge ptr to skip the head bytes
  (char*)ptr += sizeof(long);
  
  // Do some bookkeeping.
  struct mem_region M;
  memset( &M, 0x00, sizeof( mem_region ) );
  M.ptr = (char*)ptr - sizeof(long);
  M.size = size;
  strncpy( M.file, file, 64 );
  M.line = line;
  snprintf( memprint_buf, MEMPRINT_BUFSIZE, "%s:%lu", file, line );
  M.pass = Vmem_seen[ get_crc(memprint_buf,strlen(memprint_buf)) ]++;
  M.freed = false;
  
  // Add the head byte
  memcpy( (char*)ptr - sizeof(long), &Vchk_head, sizeof(long) );
  
  // Add the tail byte
  memcpy( (char*)ptr + M.size, &Vchk_tail, sizeof(long) );
  
  // Record the allocation
  Vmem_acct[ ptr ] = M;

  if( Vmalloc_verbose ) printf( "Vmalloc allocated %lu bytes at %p for %s:%ld\n",
				size, ptr, file, line );

  return ptr;
}

/** @brief internal memory freeing routine
    @param ptr pointer to memory to be free'd
    @param file we are called from
    @param line we are called from
    @sa _Vmalloc() Vfree()

    @attention
    This routine should be called via the Vmalloc() macro only.

    This free wrapper checks for errors and does extra validation.
*/
void _Vfree( void * ptr, const char* file, const long line ) {
  if( ptr == NULL ) return;
  // Check the books for this allocations.
  mem_region &M = Vmemget( ptr, "Vfree", file, line );

  // Check for attempts to free a realloced region.
  if( M.realloced ) {
    fprintf(stderr, "Vfree was called at %s:%ld with a pointer that had previously been realloced, %p\n -> %s\n",
	    file, line, ptr, Vmemprint( M ) );
    Vchk_accounting = false;
    exit(EXIT_FAILURE);
  }

  // Check for attempts to free a freed region.
  if( M.freed ) {
    mem_region F = Vmem_frees[ ptr ];
    fprintf(stderr, "Vfree was called at %s:%ld with a pointer that had previously been freed, %p\n -> freed at %s:%ld\n -> %s\n",
	    file, line, ptr, F.file, F.line, Vmemprint( M ) );
    Vchk_accounting = false;
    exit(EXIT_FAILURE);
  }

  // Check the region first
  _Vmemcheck( ptr, file, line, "checking during free" );
  
  // Adjust the ptr back to being what malloc originally gave us.
  void* full_ptr = (char*)ptr - sizeof(long) * (M.replaced ? 2 : 1 );
  
  // Ok, mark us as done.
  M.freed = true;

  // Clear the memory so that if the caller tries to access it later they get NULL's
  memset( full_ptr, 0x00, M.size + sizeof(long) * (M.replaced ? 2 : 1 ) );

  // Add it to the free list
  mem_region F = M;
  strncpy( F.file, file, 64 );
  F.line = line;
  Vmem_frees[ ptr ] = F;

  if( Vmalloc_verbose ) printf( "Vfree reclaimed %lu bytes at %p for %s:%ld\n",
				M.size, ptr, file, line );
  // Actually free the memory.
  free( full_ptr );
}

/** @brief Add a note to an internal memory allocation for tracking down memory problems.
    @param ptr pointer to memory to be annotated
    @param file we are called from
    @param line we are called from
    @param fmt note to attach
    @sa Vmemnote()

    @attention
    This routine should be called via the Vmemnote() macro only.

    This routing add a note to a region of memory allocated via
    Vmalloc().  This note will be displayed in any error messages
    associated with this region.
*/
void _Vmemnote( void *ptr, const char* file, const long line, const char* fmt, ... ) {
  mem_region &M = Vmemget( ptr, "Vmemnote", file, line );
  va_list va;
  va_start( va, fmt );
  vsnprintf( memprint_buf, MEMPRINT_BUFSIZE, fmt, va );
  va_end( va );
  strncpy( M.note, memprint_buf, 128 );
}

/** @brief internal memory checking routine
    @param ptr pointer to memory to be checked
    @param file we are called from
    @param line we are called from
    @param fmt note to attach
    @sa Vmemcheck()

    @attention
    This routine should be called via the Vmemcheck() macro only.

    This routing checks the head and tail check bytes on a given
    region of memory.  It will exit if errors are found.
*/
void _Vmemcheck( void *ptr, const char* file, const long line, const char* const fmt, ... ) {
  bool error = false;
  bool error_in_tail = false;
  char *buf;
  mem_region &M = Vmemget( ptr, "Vmemcheck", file, line );

  // Allocate our buffer
  buf = (char*)malloc(MEMPRINT_BUFSIZE);

  // Process the args
  va_list va;
  va_start( va, fmt );
  vsnprintf( buf, 2048, fmt, va );
  va_end( va );

  // Check the head bytes
  if( memcmp( (char*)ptr - sizeof(long), &Vchk_head, sizeof(long) ) != 0 ) {
    error_in_tail = false;
    error = true;
  }
  
  // Check the tail bytes
  if( memcmp( (char*)ptr + M.size, &Vchk_tail, sizeof(long) ) != 0 ) {
    error_in_tail = true;
    error = true;
  }


  if( error ) {
    fprintf( stderr, "\nVmemcheck called from %s:%ld detected an %s on memory at %p:\n -> allocated at %s\n",
	     file, line, error_in_tail ? "overrun" : "underrun", ptr, Vmemprint(M) );
    fprintf( stderr, "%s\n", VhexdumpPointS( (char*)(error_in_tail ? (char*)ptr + M.size : ptr ) - 16, 32, (error_in_tail ? (char*)ptr + M.size : ptr ) ).c_str() );
    fprintf( stderr, "%s check bytes were 0x%08X when they should have been 0x%08X.\n",
	     error_in_tail ? "Tail" : "Head",
	     error_in_tail ? *(long*)((char*)ptr+M.size) : *((long*)((char*)ptr-sizeof(long))), 
	     error_in_tail ? Vchk_tail : Vchk_head 
	     );
    fprintf( stderr, "Message: %s\n", buf );
    Vchk_accounting = false;
    free( buf );
    exit(EXIT_FAILURE);
  }
  free( buf );
}

/** dump a region of memory to stdout
    @param ptr pointer to start display at
    @param size size of region to display
*/
void Vhexdump( const void* ptr, const long size ) {  VhexdumpPoint( ptr, size, NULL ); }

/** dump a region of memory to stdout, point out a specific address
    @param ptr pointer to start display at
    @param size size of region to display
    @param point_out pointer to display as a marker, NULL if you don't want a marker.
*/
void VhexdumpPoint( const void* ptr, const long size, void* point_out ) { printf("%s\n",VhexdumpPointS( ptr, size, point_out ).c_str() ); }  

/** @brief return a string containing a hexdump of a memory region.
    @param ptr pointer to start display at
    @param size size of region to display
    @param point_out pointer to display as a marker, NULL if you don't want a marker.
    @return string object containing the hexdump suitable for printing

    @attention
    This function is available from C++ only.
*/  
std::string VhexdumpPointS( const void* ptr, const long size, void* point_out ) {
  std::string hex;
  if( ptr == NULL ) {
    hex = " 0x00000000 - cowardly refusing to dump a NULL pointer.\n";
    return hex;
  }
  char *buf = (char*)ptr;
  char cbuf[64];
  char pbuf[9]; memset(pbuf,0x00,9);
  short pos = 0;
  short loc = 0;
  bool seen_point = false;
  for( unsigned int i=0; i<size; i++ ) {
    if( loc > 7 ) {
      loc = 0;
      hex += " - ";
      snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-8));
      hex += cbuf;
      hex += " ";
      snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-4));
      hex += cbuf;
      hex += " - ";
      hex += pbuf;
      hex += "\n";
      if( point_out
	  && (long)((char*)point_out - (char*)(buf+pos-8)) < 8
	  && (long)((char*)point_out - (char*)(buf+pos-8)) >= 0
	  ) {
	seen_point = true;
	long ptr_pos = (long)((char*)point_out - (char*)(buf+pos-8));
	snprintf(cbuf,16,"*%p",point_out);
	hex += cbuf;
	hex += " -> ";
	for( long n = 0; n < 8; n++ ) {
	  if( n == 4 )
	    hex += "  ";
	  if( n == ptr_pos ) 
	    hex += "^";
	  hex += "   ";
	}
	hex += "\n";
      }
    }
    if( loc == 4 )
      hex += "  ";
    if( loc == 0 ) {
      snprintf(cbuf,16," %p",&buf[pos]);
      hex += cbuf;
      hex += " ->";
    }
    unsigned char pbyte = (unsigned char)buf[pos];
    snprintf(cbuf,16," %02x",pbyte); hex += cbuf;
    if( pbyte > 32 && pbyte < 127 )
      pbuf[loc] = pbyte;
    else
      pbuf[loc] = '.';
    loc++;
    pos++;
  }
  if( loc < 9 ) {
    for( int i = loc; i < 8; i++ ) {
      pbuf[i] = ' ';
      if( i == 4 )
        hex += "  ";
      hex += " --";
    }
    hex += "   ";
    printf( "loc = %ld\n", loc );
    if( loc >= 4 ) {
      snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-loc));
      hex += cbuf;
    } else
      hex += "0xNA      ";
    hex += " ";
    if( loc >= 8 ) {
      snprintf(cbuf,64,"0x%08lx",*(long*)(buf+pos-loc+4));
      hex += cbuf;
    } else
      hex += "0xNA      ";
    hex += " - ";
    hex += pbuf;
    if( point_out
	&& (long)((char*)point_out - (char*)(buf+pos-8)) < 8
	&& (long)((char*)point_out - (char*)(buf+pos-8)) >= 0
	) {
      seen_point = true;
      long ptr_pos = (long)((char*)point_out - (char*)(buf+pos-8));
      snprintf(cbuf,16,"\n*%p",point_out);
      hex += cbuf;
      hex += " -> ";
      for( long n = 0; n < 8; n++ ) {
	if( n == 4 )
	  hex += "  ";
	if( n == ptr_pos ) 
	  hex += "^";
	hex += "   ";
      }
    }
  }
  if( point_out && !seen_point ) {
    snprintf(cbuf,16,"\n*%p",point_out);
    hex += cbuf;
    hex += " ->   NOT FOUND!\n";
  }
  return hex;
}

/** @example vmalloc1.C Example of malloc'ing a C++ class and failing to free it. */
/** @example vmalloc2.c Example of failing to free a memory region that also illustrates the use of Vmemnote(). */
/** @example vmalloc3.c Example of underrun detection. */
/** @example vmalloc4.c Example of overrun detection. */
/** @example vmalloc5.c Example of the Vrealloc() and Vhexdump() */
/** @example vmalloc6.c Example of double freeing a Vmalloc()'ed pointer */


syntax highlighted by Code2HTML, v. 0.9.1