text: /*
  ** 2007 August 15
  **
  ** The author disclaims copyright to this source code.  In place of
  ** a legal notice, here is a blessing:
  **
  **    May you do good and not evil.
  **    May you find forgiveness for yourself and forgive others.
  **    May you share freely, never taking more than you give.
  **
  *************************************************************************
  **
  ** This file contains code used to implement test interfaces to the
  ** memory allocation subsystem.
  */
  #include "sqliteInt.h"
  #if defined(INCLUDE_SQLITE_TCL_H)
  #  include "sqlite_tcl.h"
  #else
  #  include "tcl.h"
  #endif
  #include <stdlib.h>
  #include <string.h>
  #include <assert.h>
  
  /*
  ** This structure is used to encapsulate the global state variables used
  ** by malloc() fault simulation.
  */
  static struct MemFault {
    int iCountdown;         /* Number of pending successes before a failure */
    int nRepeat;            /* Number of times to repeat the failure */
    int nBenign;            /* Number of benign failures seen since last config */
    int nFail;              /* Number of failures seen since last config */
    u8 enable;              /* True if enabled */
    int isInstalled;        /* True if the fault simulation layer is installed */
    int isBenignMode;       /* True if malloc failures are considered benign */
    sqlite3_mem_methods m;  /* 'Real' malloc implementation */
  } memfault;
  
  /*
  ** This routine exists as a place to set a breakpoint that will
  ** fire on any simulated malloc() failure.
  */
  static void sqlite3Fault(void){
    static int cnt = 0;
    cnt++;
  }
  
  /*
  ** Check to see if a fault should be simulated.  Return true to simulate
  ** the fault.  Return false if the fault should not be simulated.
  */
  static int faultsimStep(void){
    if( likely(!memfault.enable) ){
      return 0;
    }
    if( memfault.iCountdown>0 ){
      memfault.iCountdown--;
      return 0;
    }
    sqlite3Fault();
    memfault.nFail++;
    if( memfault.isBenignMode>0 ){
      memfault.nBenign++;
    }
    memfault.nRepeat--;
    if( memfault.nRepeat<=0 ){
      memfault.enable = 0;
    }
    return 1;
  }
  
  /*
  ** A version of sqlite3_mem_methods.xMalloc() that includes fault simulation
  ** logic.
  */
  static void *faultsimMalloc(int n){
    void *p = 0;
    if( !faultsimStep() ){
      p = memfault.m.xMalloc(n);
    }
    return p;
  }
  
  
  /*
  ** A version of sqlite3_mem_methods.xRealloc() that includes fault simulation
  ** logic.
  */
  static void *faultsimRealloc(void *pOld, int n){
    void *p = 0;
    if( !faultsimStep() ){
      p = memfault.m.xRealloc(pOld, n);
    }
    return p;
  }
  
  /*
  ** The following method calls are passed directly through to the underlying
  ** malloc system:
  **
  **     xFree
  **     xSize
  **     xRoundup
  **     xInit
  **     xShutdown
  */
  static void faultsimFree(void *p){
    memfault.m.xFree(p);
  }
  static int faultsimSize(void *p){
    return memfault.m.xSize(p);
  }
  static int faultsimRoundup(int n){
    return memfault.m.xRoundup(n);
  }
  static int faultsimInit(void *p){
    return memfault.m.xInit(memfault.m.pAppData);
  }
  static void faultsimShutdown(void *p){
    memfault.m.xShutdown(memfault.m.pAppData);
  }
  
  /*
  ** This routine configures the malloc failure simulation.  After
  ** calling this routine, the next nDelay mallocs will succeed, followed
  ** by a block of nRepeat failures, after which malloc() calls will begin
  ** to succeed again.
  */
  static void faultsimConfig(int nDelay, int nRepeat){
    memfault.iCountdown = nDelay;
    memfault.nRepeat = nRepeat;
    memfault.nBenign = 0;
    memfault.nFail = 0;
    memfault.enable = nDelay>=0;
  
    /* Sometimes, when running multi-threaded tests, the isBenignMode
    ** variable is not properly incremented/decremented so that it is
    ** 0 when not inside a benign malloc block. This doesn't affect
    ** the multi-threaded tests, as they do not use this system. But
    ** it does affect OOM tests run later in the same process. So
    ** zero the variable here, just to be sure.
    */
    memfault.isBenignMode = 0;
  }
  
  /*
  ** Return the number of faults (both hard and benign faults) that have
  ** occurred since the injector was last configured.
  */
  static int faultsimFailures(void){
    return memfault.nFail;
  }
  
  /*
  ** Return the number of benign faults that have occurred since the
  ** injector was last configured.
  */
  static int faultsimBenignFailures(void){
    return memfault.nBenign;
  }
  
  /*
  ** Return the number of successes that will occur before the next failure.
  ** If no failures are scheduled, return -1.
  */
  static int faultsimPending(void){
    if( memfault.enable ){
      return memfault.iCountdown;
    }else{
      return -1;
    }
  }
  
  
  static void faultsimBeginBenign(void){
    memfault.isBenignMode++;
  }
  static void faultsimEndBenign(void){
    memfault.isBenignMode--;
  }
  
  /*
  ** Add or remove the fault-simulation layer using sqlite3_config(). If
  ** the argument is non-zero, the
  */
  static int faultsimInstall(int install){
    static struct sqlite3_mem_methods m = {
      faultsimMalloc,                   /* xMalloc */
      faultsimFree,                     /* xFree */
      faultsimRealloc,                  /* xRealloc */
      faultsimSize,                     /* xSize */
      faultsimRoundup,                  /* xRoundup */
      faultsimInit,                     /* xInit */
      faultsimShutdown,                 /* xShutdown */
      0                                 /* pAppData */
    };
    int rc;
  
    install = (install ? 1 : 0);
    assert(memfault.isInstalled==1 || memfault.isInstalled==0);
  
    if( install==memfault.isInstalled ){
      return SQLITE_ERROR;
    }
  
    if( install ){
      rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memfault.m);
      assert(memfault.m.xMalloc);
      if( rc==SQLITE_OK ){
        rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &m);
      }
      sqlite3_test_control(SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,
          faultsimBeginBenign, faultsimEndBenign
      );
    }else{
      sqlite3_mem_methods m2;
      assert(memfault.m.xMalloc);
  
      /* One should be able to reset the default memory allocator by storing
      ** a zeroed allocator then calling GETMALLOC. */
      memset(&m2, 0, sizeof(m2));
      sqlite3_config(SQLITE_CONFIG_MALLOC, &m2);
      sqlite3_config(SQLITE_CONFIG_GETMALLOC, &m2);
      assert( memcmp(&m2, &memfault.m, sizeof(m2))==0 );
  
      rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memfault.m);
      sqlite3_test_control(SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,
          (void*)0, (void*)0);
    }
  
    if( rc==SQLITE_OK ){
      memfault.isInstalled = 1;
    }
    return rc;
  }
  
  #ifdef SQLITE_TEST
  
  /*
  ** This function is implemented in main.c. Returns a pointer to a static
  ** buffer containing the symbolic SQLite error code that corresponds to
  ** the least-significant 8-bits of the integer passed as an argument.
  ** For example:
  **
  **   sqlite3ErrName(1) -> "SQLITE_ERROR"
  */
  extern const char *sqlite3ErrName(int);
  
  /*
  ** Transform pointers to text and back again
  */
  static void pointerToText(void *p, char *z){
    static const char zHex[] = "0123456789abcdef";
    int i, k;
    unsigned int u;
    sqlite3_uint64 n;
    if( p==0 ){
      strcpy(z, "0");
      return;
    }
    if( sizeof(n)==sizeof(p) ){
      memcpy(&n, &p, sizeof(p));
    }else if( sizeof(u)==sizeof(p) ){
      memcpy(&u, &p, sizeof(u));
      n = u;
    }else{
      assert( 0 );
    }
    for(i=0, k=sizeof(p)*2-1; i<sizeof(p)*2; i++, k--){
      z[k] = zHex[n&0xf];
      n >>= 4;
    }
    z[sizeof(p)*2] = 0;
  }
  static int hexToInt(int h){
    if( h>='0' && h<='9' ){
      return h - '0';
    }else if( h>='a' && h<='f' ){
      return h - 'a' + 10;
    }else{
      return -1;
    }
  }
  static int textToPointer(const char *z, void **pp){
    sqlite3_uint64 n = 0;
    int i;
    unsigned int u;
    for(i=0; i<sizeof(void*)*2 && z[0]; i++){
      int v;
      v = hexToInt(*z++);
      if( v<0 ) return TCL_ERROR;
      n = n*16 + v;
    }
    if( *z!=0 ) return TCL_ERROR;
    if( sizeof(n)==sizeof(*pp) ){
      memcpy(pp, &n, sizeof(n));
    }else if( sizeof(u)==sizeof(*pp) ){
      u = (unsigned int)n;
      memcpy(pp, &u, sizeof(u));
    }else{
      assert( 0 );
    }
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_malloc  NBYTES
  **
  ** Raw test interface for sqlite3_malloc().
  */
  static int SQLITE_TCLAPI test_malloc(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int nByte;
    void *p;
    char zOut[100];
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "NBYTES");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &nByte) ) return TCL_ERROR;
    p = sqlite3_malloc((unsigned)nByte);
    pointerToText(p, zOut);
    Tcl_AppendResult(interp, zOut, NULL);
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_realloc  PRIOR  NBYTES
  **
  ** Raw test interface for sqlite3_realloc().
  */
  static int SQLITE_TCLAPI test_realloc(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int nByte;
    void *pPrior, *p;
    char zOut[100];
    if( objc!=3 ){
      Tcl_WrongNumArgs(interp, 1, objv, "PRIOR NBYTES");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[2], &nByte) ) return TCL_ERROR;
    if( textToPointer(Tcl_GetString(objv[1]), &pPrior) ){
      Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
      return TCL_ERROR;
    }
    p = sqlite3_realloc(pPrior, (unsigned)nByte);
    pointerToText(p, zOut);
    Tcl_AppendResult(interp, zOut, NULL);
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_free  PRIOR
  **
  ** Raw test interface for sqlite3_free().
  */
  static int SQLITE_TCLAPI test_free(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    void *pPrior;
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "PRIOR");
      return TCL_ERROR;
    }
    if( textToPointer(Tcl_GetString(objv[1]), &pPrior) ){
      Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
      return TCL_ERROR;
    }
    sqlite3_free(pPrior);
    return TCL_OK;
  }
  
  /*
  ** These routines are in test_hexio.c
  */
  int sqlite3TestHexToBin(const char *, int, char *);
  int sqlite3TestBinToHex(char*,int);
  
  /*
  ** Usage:    memset  ADDRESS  SIZE  HEX
  **
  ** Set a chunk of memory (obtained from malloc, probably) to a
  ** specified hex pattern.
  */
  static int SQLITE_TCLAPI test_memset(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    void *p;
    int size, n, i;
    char *zHex;
    char *zOut;
    char zBin[100];
  
    if( objc!=4 ){
      Tcl_WrongNumArgs(interp, 1, objv, "ADDRESS SIZE HEX");
      return TCL_ERROR;
    }
    if( textToPointer(Tcl_GetString(objv[1]), &p) ){
      Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[2], &size) ){
      return TCL_ERROR;
    }
    if( size<=0 ){
      Tcl_AppendResult(interp, "size must be positive", (char*)0);
      return TCL_ERROR;
    }
    zHex = Tcl_GetStringFromObj(objv[3], &n);
    if( n>sizeof(zBin)*2 ) n = sizeof(zBin)*2;
    n = sqlite3TestHexToBin(zHex, n, zBin);
    if( n==0 ){
      Tcl_AppendResult(interp, "no data", (char*)0);
      return TCL_ERROR;
    }
    zOut = p;
    for(i=0; i<size; i++){
      zOut[i] = zBin[i%n];
    }
    return TCL_OK;
  }
  
  /*
  ** Usage:    memget  ADDRESS  SIZE
  **
  ** Return memory as hexadecimal text.
  */
  static int SQLITE_TCLAPI test_memget(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    void *p;
    int size, n;
    char *zBin;
    char zHex[100];
  
    if( objc!=3 ){
      Tcl_WrongNumArgs(interp, 1, objv, "ADDRESS SIZE");
      return TCL_ERROR;
    }
    if( textToPointer(Tcl_GetString(objv[1]), &p) ){
      Tcl_AppendResult(interp, "bad pointer: ", Tcl_GetString(objv[1]), (char*)0);
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[2], &size) ){
      return TCL_ERROR;
    }
    if( size<=0 ){
      Tcl_AppendResult(interp, "size must be positive", (char*)0);
      return TCL_ERROR;
    }
    zBin = p;
    while( size>0 ){
      if( size>(sizeof(zHex)-1)/2 ){
        n = (sizeof(zHex)-1)/2;
      }else{
        n = size;
      }
      memcpy(zHex, zBin, n);
      zBin += n;
      size -= n;
      sqlite3TestBinToHex(zHex, n);
      Tcl_AppendResult(interp, zHex, (char*)0);
    }
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_memory_used
  **
  ** Raw test interface for sqlite3_memory_used().
  */
  static int SQLITE_TCLAPI test_memory_used(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(sqlite3_memory_used()));
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_memory_highwater ?RESETFLAG?
  **
  ** Raw test interface for sqlite3_memory_highwater().
  */
  static int SQLITE_TCLAPI test_memory_highwater(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int resetFlag = 0;
    if( objc!=1 && objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "?RESET?");
      return TCL_ERROR;
    }
    if( objc==2 ){
      if( Tcl_GetBooleanFromObj(interp, objv[1], &resetFlag) ) return TCL_ERROR;
    }
    Tcl_SetObjResult(interp,
       Tcl_NewWideIntObj(sqlite3_memory_highwater(resetFlag)));
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_memdebug_backtrace DEPTH
  **
  ** Set the depth of backtracing.  If SQLITE_MEMDEBUG is not defined
  ** then this routine is a no-op.
  */
  static int SQLITE_TCLAPI test_memdebug_backtrace(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int depth;
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "DEPT");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &depth) ) return TCL_ERROR;
  #ifdef SQLITE_MEMDEBUG
    {
      extern void sqlite3MemdebugBacktrace(int);
      sqlite3MemdebugBacktrace(depth);
    }
  #endif
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_memdebug_dump  FILENAME
  **
  ** Write a summary of unfreed memory to FILENAME.
  */
  static int SQLITE_TCLAPI test_memdebug_dump(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
      return TCL_ERROR;
    }
  #if defined(SQLITE_MEMDEBUG) || defined(SQLITE_MEMORY_SIZE) \
       || defined(SQLITE_POW2_MEMORY_SIZE)
    {
      extern void sqlite3MemdebugDump(const char*);
      sqlite3MemdebugDump(Tcl_GetString(objv[1]));
    }
  #endif
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_memdebug_malloc_count
  **
  ** Return the total number of times malloc() has been called.
  */
  static int SQLITE_TCLAPI test_memdebug_malloc_count(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int nMalloc = -1;
    if( objc!=1 ){
      Tcl_WrongNumArgs(interp, 1, objv, "");
      return TCL_ERROR;
    }
  #if defined(SQLITE_MEMDEBUG)
    {
      extern int sqlite3MemdebugMallocCount();
      nMalloc = sqlite3MemdebugMallocCount();
    }
  #endif
    Tcl_SetObjResult(interp, Tcl_NewIntObj(nMalloc));
    return TCL_OK;
  }
  
  
  /*
  ** Usage:    sqlite3_memdebug_fail  COUNTER  ?OPTIONS?
  **
  ** where options are:
  **
  **     -repeat    <count>
  **     -benigncnt <varname>
  **
  ** Arrange for a simulated malloc() failure after COUNTER successes.
  ** If a repeat count is specified, the fault is repeated that many
  ** times.
  **
  ** Each call to this routine overrides the prior counter value.
  ** This routine returns the number of simulated failures that have
  ** happened since the previous call to this routine.
  **
  ** To disable simulated failures, use a COUNTER of -1.
  */
  static int SQLITE_TCLAPI test_memdebug_fail(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int ii;
    int iFail;
    int nRepeat = 1;
    Tcl_Obj *pBenignCnt = 0;
    int nBenign;
    int nFail = 0;
  
    if( objc<2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "COUNTER ?OPTIONS?");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &iFail) ) return TCL_ERROR;
  
    for(ii=2; ii<objc; ii+=2){
      int nOption;
      char *zOption = Tcl_GetStringFromObj(objv[ii], &nOption);
      char *zErr = 0;
  
      if( nOption>1 && strncmp(zOption, "-repeat", nOption)==0 ){
        if( ii==(objc-1) ){
          zErr = "option requires an argument: ";
        }else{
          if( Tcl_GetIntFromObj(interp, objv[ii+1], &nRepeat) ){
            return TCL_ERROR;
          }
        }
      }else if( nOption>1 && strncmp(zOption, "-benigncnt", nOption)==0 ){
        if( ii==(objc-1) ){
          zErr = "option requires an argument: ";
        }else{
          pBenignCnt = objv[ii+1];
        }
      }else{
        zErr = "unknown option: ";
      }
  
      if( zErr ){
        Tcl_AppendResult(interp, zErr, zOption, 0);
        return TCL_ERROR;
      }
    }
  
    nBenign = faultsimBenignFailures();
    nFail = faultsimFailures();
    faultsimConfig(iFail, nRepeat);
  
    if( pBenignCnt ){
      Tcl_ObjSetVar2(interp, pBenignCnt, 0, Tcl_NewIntObj(nBenign), 0);
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(nFail));
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_memdebug_pending
  **
  ** Return the number of malloc() calls that will succeed before a
  ** simulated failure occurs. A negative return value indicates that
  ** no malloc() failure is scheduled.
  */
  static int SQLITE_TCLAPI test_memdebug_pending(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int nPending;
    if( objc!=1 ){
      Tcl_WrongNumArgs(interp, 1, objv, "");
      return TCL_ERROR;
    }
    nPending = faultsimPending();
    Tcl_SetObjResult(interp, Tcl_NewIntObj(nPending));
    return TCL_OK;
  }
  
  /*
  ** The following global variable keeps track of the number of tests
  ** that have run.  This variable is only useful when running in the
  ** debugger.
  */
  static int sqlite3_memdebug_title_count = 0;
  
  /*
  ** Usage:    sqlite3_memdebug_settitle TITLE
  **
  ** Set a title string stored with each allocation.  The TITLE is
  ** typically the name of the test that was running when the
  ** allocation occurred.  The TITLE is stored with the allocation
  ** and can be used to figure out which tests are leaking memory.
  **
  ** Each title overwrite the previous.
  */
  static int SQLITE_TCLAPI test_memdebug_settitle(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    sqlite3_memdebug_title_count++;
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "TITLE");
      return TCL_ERROR;
    }
  #ifdef SQLITE_MEMDEBUG
    {
      const char *zTitle;
      extern int sqlite3MemdebugSettitle(const char*);
      zTitle = Tcl_GetString(objv[1]);
      sqlite3MemdebugSettitle(zTitle);
    }
  #endif
    return TCL_OK;
  }
  
  #define MALLOC_LOG_FRAMES  10
  #define MALLOC_LOG_KEYINTS (                                              \
      10 * ((sizeof(int)>=sizeof(void*)) ? 1 : sizeof(void*)/sizeof(int))   \
  )
  static Tcl_HashTable aMallocLog;
  static int mallocLogEnabled = 0;
  
  typedef struct MallocLog MallocLog;
  struct MallocLog {
    int nCall;
    int nByte;
  };
  
  #ifdef SQLITE_MEMDEBUG
  static void test_memdebug_callback(int nByte, int nFrame, void **aFrame){
    if( mallocLogEnabled ){
      MallocLog *pLog;
      Tcl_HashEntry *pEntry;
      int isNew;
  
      int aKey[MALLOC_LOG_KEYINTS];
      unsigned int nKey = sizeof(int)*MALLOC_LOG_KEYINTS;
  
      memset(aKey, 0, nKey);
      if( (sizeof(void*)*nFrame)<nKey ){
        nKey = nFrame*sizeof(void*);
      }
      memcpy(aKey, aFrame, nKey);
  
      pEntry = Tcl_CreateHashEntry(&aMallocLog, (const char *)aKey, &isNew);
      if( isNew ){
        pLog = (MallocLog *)Tcl_Alloc(sizeof(MallocLog));
        memset(pLog, 0, sizeof(MallocLog));
        Tcl_SetHashValue(pEntry, (ClientData)pLog);
      }else{
        pLog = (MallocLog *)Tcl_GetHashValue(pEntry);
      }
  
      pLog->nCall++;
      pLog->nByte += nByte;
    }
  }
  #endif /* SQLITE_MEMDEBUG */
  
  static void test_memdebug_log_clear(void){
    Tcl_HashSearch search;
    Tcl_HashEntry *pEntry;
    for(
      pEntry=Tcl_FirstHashEntry(&aMallocLog, &search);
      pEntry;
      pEntry=Tcl_NextHashEntry(&search)
    ){
      MallocLog *pLog = (MallocLog *)Tcl_GetHashValue(pEntry);
      Tcl_Free((char *)pLog);
    }
    Tcl_DeleteHashTable(&aMallocLog);
    Tcl_InitHashTable(&aMallocLog, MALLOC_LOG_KEYINTS);
  }
  
  static int SQLITE_TCLAPI test_memdebug_log(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    static int isInit = 0;
    int iSub;
  
    static const char *MB_strs[] = { "start", "stop", "dump", "clear", "sync" };
    enum MB_enum {
        MB_LOG_START, MB_LOG_STOP, MB_LOG_DUMP, MB_LOG_CLEAR, MB_LOG_SYNC
    };
  
    if( !isInit ){
  #ifdef SQLITE_MEMDEBUG
      extern void sqlite3MemdebugBacktraceCallback(
          void (*xBacktrace)(int, int, void **));
      sqlite3MemdebugBacktraceCallback(test_memdebug_callback);
  #endif
      Tcl_InitHashTable(&aMallocLog, MALLOC_LOG_KEYINTS);
      isInit = 1;
    }
  
    if( objc<2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
    }
    if( Tcl_GetIndexFromObj(interp, objv[1], MB_strs, "sub-command", 0, &iSub) ){
      return TCL_ERROR;
    }
  
    switch( (enum MB_enum)iSub ){
      case MB_LOG_START:
        mallocLogEnabled = 1;
        break;
      case MB_LOG_STOP:
        mallocLogEnabled = 0;
        break;
      case MB_LOG_DUMP: {
        Tcl_HashSearch search;
        Tcl_HashEntry *pEntry;
        Tcl_Obj *pRet = Tcl_NewObj();
  
        assert(sizeof(Tcl_WideInt)>=sizeof(void*));
  
        for(
          pEntry=Tcl_FirstHashEntry(&aMallocLog, &search);
          pEntry;
          pEntry=Tcl_NextHashEntry(&search)
        ){
          Tcl_Obj *apElem[MALLOC_LOG_FRAMES+2];
          MallocLog *pLog = (MallocLog *)Tcl_GetHashValue(pEntry);
          Tcl_WideInt *aKey = (Tcl_WideInt *)Tcl_GetHashKey(&aMallocLog, pEntry);
          int ii;
  
          apElem[0] = Tcl_NewIntObj(pLog->nCall);
          apElem[1] = Tcl_NewIntObj(pLog->nByte);
          for(ii=0; ii<MALLOC_LOG_FRAMES; ii++){
            apElem[ii+2] = Tcl_NewWideIntObj(aKey[ii]);
          }
  
          Tcl_ListObjAppendElement(interp, pRet,
              Tcl_NewListObj(MALLOC_LOG_FRAMES+2, apElem)
          );
        }
  
        Tcl_SetObjResult(interp, pRet);
        break;
      }
      case MB_LOG_CLEAR: {
        test_memdebug_log_clear();
        break;
      }
  
      case MB_LOG_SYNC: {
  #ifdef SQLITE_MEMDEBUG
        extern void sqlite3MemdebugSync();
        test_memdebug_log_clear();
        mallocLogEnabled = 1;
        sqlite3MemdebugSync();
  #endif
        break;
      }
    }
  
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_pagecache SIZE N
  **
  ** Set the page-cache memory buffer using SQLITE_CONFIG_PAGECACHE.
  ** The buffer is static and is of limited size.  N might be
  ** adjusted downward as needed to accommodate the requested size.
  ** The revised value of N is returned.
  **
  ** A negative SIZE causes the buffer pointer to be NULL.
  */
  static int SQLITE_TCLAPI test_config_pagecache(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int sz, N;
    Tcl_Obj *pRes;
    static char *buf = 0;
    if( objc!=3 ){
      Tcl_WrongNumArgs(interp, 1, objv, "SIZE N");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &sz) ) return TCL_ERROR;
    if( Tcl_GetIntFromObj(interp, objv[2], &N) ) return TCL_ERROR;
    free(buf);
    buf = 0;
  
    /* Set the return value */
    pRes = Tcl_NewObj();
    Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(sqlite3GlobalConfig.szPage));
    Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(sqlite3GlobalConfig.nPage));
    Tcl_SetObjResult(interp, pRes);
  
    if( sz<0 ){
      sqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, 0);
    }else{
      buf = malloc( sz*N );
      sqlite3_config(SQLITE_CONFIG_PAGECACHE, buf, sz, N);
    }
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_alt_pcache INSTALL_FLAG DISCARD_CHANCE PRNG_SEED
  **
  ** Set up the alternative test page cache.  Install if INSTALL_FLAG is
  ** true and uninstall (reverting to the default page cache) if INSTALL_FLAG
  ** is false.  DISCARD_CHANGE is an integer between 0 and 100 inclusive
  ** which determines the chance of discarding a page when unpinned.  100
  ** is certainty.  0 is never.  PRNG_SEED is the pseudo-random number generator
  ** seed.
  */
  static int SQLITE_TCLAPI test_alt_pcache(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int installFlag;
    int discardChance = 0;
    int prngSeed = 0;
    int highStress = 0;
    extern void installTestPCache(int,unsigned,unsigned,unsigned);
    if( objc<2 || objc>5 ){
      Tcl_WrongNumArgs(interp, 1, objv,
          "INSTALLFLAG DISCARDCHANCE PRNGSEEED HIGHSTRESS");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &installFlag) ) return TCL_ERROR;
    if( objc>=3 && Tcl_GetIntFromObj(interp, objv[2], &discardChance) ){
       return TCL_ERROR;
    }
    if( objc>=4 && Tcl_GetIntFromObj(interp, objv[3], &prngSeed) ){
       return TCL_ERROR;
    }
    if( objc>=5 && Tcl_GetIntFromObj(interp, objv[4], &highStress) ){
      return TCL_ERROR;
    }
    if( discardChance<0 || discardChance>100 ){
      Tcl_AppendResult(interp, "discard-chance should be between 0 and 100",
                       (char*)0);
      return TCL_ERROR;
    }
    installTestPCache(installFlag, (unsigned)discardChance, (unsigned)prngSeed,
                      (unsigned)highStress);
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_memstatus BOOLEAN
  **
  ** Enable or disable memory status reporting using SQLITE_CONFIG_MEMSTATUS.
  */
  static int SQLITE_TCLAPI test_config_memstatus(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int enable, rc;
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
      return TCL_ERROR;
    }
    if( Tcl_GetBooleanFromObj(interp, objv[1], &enable) ) return TCL_ERROR;
    rc = sqlite3_config(SQLITE_CONFIG_MEMSTATUS, enable);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_lookaside  SIZE  COUNT
  **
  */
  static int SQLITE_TCLAPI test_config_lookaside(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int sz, cnt;
    Tcl_Obj *pRet;
    if( objc!=3 ){
      Tcl_WrongNumArgs(interp, 1, objv, "SIZE COUNT");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &sz) ) return TCL_ERROR;
    if( Tcl_GetIntFromObj(interp, objv[2], &cnt) ) return TCL_ERROR;
    pRet = Tcl_NewObj();
    Tcl_ListObjAppendElement(
        interp, pRet, Tcl_NewIntObj(sqlite3GlobalConfig.szLookaside)
    );
    Tcl_ListObjAppendElement(
        interp, pRet, Tcl_NewIntObj(sqlite3GlobalConfig.nLookaside)
    );
    sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, cnt);
    Tcl_SetObjResult(interp, pRet);
    return TCL_OK;
  }
  
  
  /*
  ** Usage:    sqlite3_db_config_lookaside  CONNECTION  BUFID  SIZE  COUNT
  **
  ** There are two static buffers with BUFID 1 and 2.   Each static buffer
  ** is 10KB in size.  A BUFID of 0 indicates that the buffer should be NULL
  ** which will cause sqlite3_db_config() to allocate space on its own.
  */
  static int SQLITE_TCLAPI test_db_config_lookaside(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc;
    int sz, cnt;
    sqlite3 *db;
    int bufid;
    static char azBuf[2][10000];
    extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
    if( objc!=5 ){
      Tcl_WrongNumArgs(interp, 1, objv, "BUFID SIZE COUNT");
      return TCL_ERROR;
    }
    if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
    if( Tcl_GetIntFromObj(interp, objv[2], &bufid) ) return TCL_ERROR;
    if( Tcl_GetIntFromObj(interp, objv[3], &sz) ) return TCL_ERROR;
    if( Tcl_GetIntFromObj(interp, objv[4], &cnt) ) return TCL_ERROR;
    if( bufid==0 ){
      rc = sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, (void*)0, sz, cnt);
    }else if( bufid>=1 && bufid<=2 && sz*cnt<=sizeof(azBuf[0]) ){
      rc = sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, azBuf[bufid], sz,cnt);
    }else{
      Tcl_AppendResult(interp, "illegal arguments - see documentation", (char*)0);
      return TCL_ERROR;
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_heap NBYTE NMINALLOC
  */
  static int SQLITE_TCLAPI test_config_heap(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    static char *zBuf; /* Use this memory */
    int nByte;         /* Size of buffer to pass to sqlite3_config() */
    int nMinAlloc;     /* Size of minimum allocation */
    int rc;            /* Return code of sqlite3_config() */
  
    Tcl_Obj * CONST *aArg = &objv[1];
    int nArg = objc-1;
  
    if( nArg!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "NBYTE NMINALLOC");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, aArg[0], &nByte) ) return TCL_ERROR;
    if( Tcl_GetIntFromObj(interp, aArg[1], &nMinAlloc) ) return TCL_ERROR;
  
    if( nByte==0 ){
      free( zBuf );
      zBuf = 0;
      rc = sqlite3_config(SQLITE_CONFIG_HEAP, (void*)0, 0, 0);
    }else{
      zBuf = realloc(zBuf, nByte);
      rc = sqlite3_config(SQLITE_CONFIG_HEAP, zBuf, nByte, nMinAlloc);
    }
  
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_heap_size NBYTE
  */
  static int SQLITE_TCLAPI test_config_heap_size(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int nByte;         /* Size to pass to sqlite3_config() */
    int rc;            /* Return code of sqlite3_config() */
  
    Tcl_Obj * CONST *aArg = &objv[1];
    int nArg = objc-1;
  
    if( nArg!=1 ){
      Tcl_WrongNumArgs(interp, 1, objv, "NBYTE");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, aArg[0], &nByte) ) return TCL_ERROR;
  
    rc = sqlite3_config(SQLITE_CONFIG_WIN32_HEAPSIZE, nByte);
  
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_error  [DB]
  **
  ** Invoke sqlite3_config() or sqlite3_db_config() with invalid
  ** opcodes and verify that they return errors.
  */
  static int SQLITE_TCLAPI test_config_error(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    sqlite3 *db;
    extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
  
    if( objc!=2 && objc!=1 ){
      Tcl_WrongNumArgs(interp, 1, objv, "[DB]");
      return TCL_ERROR;
    }
    if( objc==2 ){
      if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
      if( sqlite3_db_config(db, 99999)!=SQLITE_ERROR ){
        Tcl_AppendResult(interp,
              "sqlite3_db_config(db, 99999) does not return SQLITE_ERROR",
              (char*)0);
        return TCL_ERROR;
      }
    }else{
      if( sqlite3_config(99999)!=SQLITE_ERROR ){
        Tcl_AppendResult(interp,
            "sqlite3_config(99999) does not return SQLITE_ERROR",
            (char*)0);
        return TCL_ERROR;
      }
    }
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_uri  BOOLEAN
  **
  ** Enables or disables interpretation of URI parameters by default using
  ** SQLITE_CONFIG_URI.
  */
  static int SQLITE_TCLAPI test_config_uri(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc;
    int bOpenUri;
  
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "BOOL");
      return TCL_ERROR;
    }
    if( Tcl_GetBooleanFromObj(interp, objv[1], &bOpenUri) ){
      return TCL_ERROR;
    }
  
    rc = sqlite3_config(SQLITE_CONFIG_URI, bOpenUri);
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
  
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_cis  BOOLEAN
  **
  ** Enables or disables the use of the covering-index scan optimization.
  ** SQLITE_CONFIG_COVERING_INDEX_SCAN.
  */
  static int SQLITE_TCLAPI test_config_cis(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc;
    int bUseCis;
  
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "BOOL");
      return TCL_ERROR;
    }
    if( Tcl_GetBooleanFromObj(interp, objv[1], &bUseCis) ){
      return TCL_ERROR;
    }
  
    rc = sqlite3_config(SQLITE_CONFIG_COVERING_INDEX_SCAN, bUseCis);
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
  
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_config_pmasz  INTEGER
  **
  ** Set the minimum PMA size.
  */
  static int SQLITE_TCLAPI test_config_pmasz(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc;
    int iPmaSz;
  
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "BOOL");
      return TCL_ERROR;
    }
    if( Tcl_GetIntFromObj(interp, objv[1], &iPmaSz) ){
      return TCL_ERROR;
    }
  
    rc = sqlite3_config(SQLITE_CONFIG_PMASZ, iPmaSz);
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
  
    return TCL_OK;
  }
  
  
  /*
  ** Usage:    sqlite3_dump_memsys3  FILENAME
  **           sqlite3_dump_memsys5  FILENAME
  **
  ** Write a summary of unfreed memsys3 allocations to FILENAME.
  */
  static int SQLITE_TCLAPI test_dump_memsys3(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
      return TCL_ERROR;
    }
  
    switch( SQLITE_PTR_TO_INT(clientData) ){
      case 3: {
  #ifdef SQLITE_ENABLE_MEMSYS3
        extern void sqlite3Memsys3Dump(const char*);
        sqlite3Memsys3Dump(Tcl_GetString(objv[1]));
        break;
  #endif
      }
      case 5: {
  #ifdef SQLITE_ENABLE_MEMSYS5
        extern void sqlite3Memsys5Dump(const char*);
        sqlite3Memsys5Dump(Tcl_GetString(objv[1]));
        break;
  #endif
      }
    }
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_status  OPCODE  RESETFLAG
  **
  ** Return a list of three elements which are the sqlite3_status() return
  ** code, the current value, and the high-water mark value.
  */
  static int SQLITE_TCLAPI test_status(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc, iValue, mxValue;
    int i, op = 0, resetFlag;
    const char *zOpName;
    static const struct {
      const char *zName;
      int op;
    } aOp[] = {
      { "SQLITE_STATUS_MEMORY_USED",         SQLITE_STATUS_MEMORY_USED         },
      { "SQLITE_STATUS_MALLOC_SIZE",         SQLITE_STATUS_MALLOC_SIZE         },
      { "SQLITE_STATUS_PAGECACHE_USED",      SQLITE_STATUS_PAGECACHE_USED      },
      { "SQLITE_STATUS_PAGECACHE_OVERFLOW",  SQLITE_STATUS_PAGECACHE_OVERFLOW  },
      { "SQLITE_STATUS_PAGECACHE_SIZE",      SQLITE_STATUS_PAGECACHE_SIZE      },
      { "SQLITE_STATUS_SCRATCH_USED",        SQLITE_STATUS_SCRATCH_USED        },
      { "SQLITE_STATUS_SCRATCH_OVERFLOW",    SQLITE_STATUS_SCRATCH_OVERFLOW    },
      { "SQLITE_STATUS_SCRATCH_SIZE",        SQLITE_STATUS_SCRATCH_SIZE        },
      { "SQLITE_STATUS_PARSER_STACK",        SQLITE_STATUS_PARSER_STACK        },
      { "SQLITE_STATUS_MALLOC_COUNT",        SQLITE_STATUS_MALLOC_COUNT        },
    };
    Tcl_Obj *pResult;
    if( objc!=3 ){
      Tcl_WrongNumArgs(interp, 1, objv, "PARAMETER RESETFLAG");
      return TCL_ERROR;
    }
    zOpName = Tcl_GetString(objv[1]);
    for(i=0; i<ArraySize(aOp); i++){
      if( strcmp(aOp[i].zName, zOpName)==0 ){
        op = aOp[i].op;
        break;
      }
    }
    if( i>=ArraySize(aOp) ){
      if( Tcl_GetIntFromObj(interp, objv[1], &op) ) return TCL_ERROR;
    }
    if( Tcl_GetBooleanFromObj(interp, objv[2], &resetFlag) ) return TCL_ERROR;
    iValue = 0;
    mxValue = 0;
    rc = sqlite3_status(op, &iValue, &mxValue, resetFlag);
    pResult = Tcl_NewObj();
    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(rc));
    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(iValue));
    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(mxValue));
    Tcl_SetObjResult(interp, pResult);
    return TCL_OK;
  }
  
  /*
  ** Usage:    sqlite3_db_status  DATABASE  OPCODE  RESETFLAG
  **
  ** Return a list of three elements which are the sqlite3_db_status() return
  ** code, the current value, and the high-water mark value.
  */
  static int SQLITE_TCLAPI test_db_status(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc, iValue, mxValue;
    int i, op = 0, resetFlag;
    const char *zOpName;
    sqlite3 *db;
    extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
    static const struct {
      const char *zName;
      int op;
    } aOp[] = {
      { "LOOKASIDE_USED",      SQLITE_DBSTATUS_LOOKASIDE_USED      },
      { "CACHE_USED",          SQLITE_DBSTATUS_CACHE_USED          },
      { "SCHEMA_USED",         SQLITE_DBSTATUS_SCHEMA_USED         },
      { "STMT_USED",           SQLITE_DBSTATUS_STMT_USED           },
      { "LOOKASIDE_HIT",       SQLITE_DBSTATUS_LOOKASIDE_HIT       },
      { "LOOKASIDE_MISS_SIZE", SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE },
      { "LOOKASIDE_MISS_FULL", SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL },
      { "CACHE_HIT",           SQLITE_DBSTATUS_CACHE_HIT           },
      { "CACHE_MISS",          SQLITE_DBSTATUS_CACHE_MISS          },
      { "CACHE_WRITE",         SQLITE_DBSTATUS_CACHE_WRITE         },
      { "DEFERRED_FKS",        SQLITE_DBSTATUS_DEFERRED_FKS        },
      { "CACHE_USED_SHARED",   SQLITE_DBSTATUS_CACHE_USED_SHARED   },
      { "CACHE_SPILL",         SQLITE_DBSTATUS_CACHE_SPILL         },
    };
    Tcl_Obj *pResult;
    if( objc!=4 ){
      Tcl_WrongNumArgs(interp, 1, objv, "DB PARAMETER RESETFLAG");
      return TCL_ERROR;
    }
    if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
    zOpName = Tcl_GetString(objv[2]);
    if( memcmp(zOpName, "SQLITE_", 7)==0 ) zOpName += 7;
    if( memcmp(zOpName, "DBSTATUS_", 9)==0 ) zOpName += 9;
    for(i=0; i<ArraySize(aOp); i++){
      if( strcmp(aOp[i].zName, zOpName)==0 ){
        op = aOp[i].op;
        break;
      }
    }
    if( i>=ArraySize(aOp) ){
      if( Tcl_GetIntFromObj(interp, objv[2], &op) ) return TCL_ERROR;
    }
    if( Tcl_GetBooleanFromObj(interp, objv[3], &resetFlag) ) return TCL_ERROR;
    iValue = 0;
    mxValue = 0;
    rc = sqlite3_db_status(db, op, &iValue, &mxValue, resetFlag);
    pResult = Tcl_NewObj();
    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(rc));
    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(iValue));
    Tcl_ListObjAppendElement(0, pResult, Tcl_NewIntObj(mxValue));
    Tcl_SetObjResult(interp, pResult);
    return TCL_OK;
  }
  
  /*
  ** install_malloc_faultsim BOOLEAN
  */
  static int SQLITE_TCLAPI test_install_malloc_faultsim(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc;
    int isInstall;
  
    if( objc!=2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "BOOLEAN");
      return TCL_ERROR;
    }
    if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[1], &isInstall) ){
      return TCL_ERROR;
    }
    rc = faultsimInstall(isInstall);
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
    return TCL_OK;
  }
  
  /*
  ** sqlite3_install_memsys3
  */
  static int SQLITE_TCLAPI test_install_memsys3(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    int rc = SQLITE_MISUSE;
  #ifdef SQLITE_ENABLE_MEMSYS3
    const sqlite3_mem_methods *sqlite3MemGetMemsys3(void);
    rc = sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetMemsys3());
  #endif
    Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE);
    return TCL_OK;
  }
  
  static int SQLITE_TCLAPI test_vfs_oom_test(
    void * clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST objv[]
  ){
    extern int sqlite3_memdebug_vfs_oom_test;
    if( objc>2 ){
      Tcl_WrongNumArgs(interp, 1, objv, "?INTEGER?");
      return TCL_ERROR;
    }else if( objc==2 ){
      int iNew;
      if( Tcl_GetIntFromObj(interp, objv[1], &iNew) ) return TCL_ERROR;
      sqlite3_memdebug_vfs_oom_test = iNew;
    }
    Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_memdebug_vfs_oom_test));
    return TCL_OK;
  }
  
  /*
  ** Register commands with the TCL interpreter.
  */
  int Sqlitetest_malloc_Init(Tcl_Interp *interp){
    static struct {
       char *zName;
       Tcl_ObjCmdProc *xProc;
       int clientData;
    } aObjCmd[] = {
       { "sqlite3_malloc",             test_malloc                   ,0 },
       { "sqlite3_realloc",            test_realloc                  ,0 },
       { "sqlite3_free",               test_free                     ,0 },
       { "memset",                     test_memset                   ,0 },
       { "memget",                     test_memget                   ,0 },
       { "sqlite3_memory_used",        test_memory_used              ,0 },
       { "sqlite3_memory_highwater",   test_memory_highwater         ,0 },
       { "sqlite3_memdebug_backtrace", test_memdebug_backtrace       ,0 },
       { "sqlite3_memdebug_dump",      test_memdebug_dump            ,0 },
       { "sqlite3_memdebug_fail",      test_memdebug_fail            ,0 },
       { "sqlite3_memdebug_pending",   test_memdebug_pending         ,0 },
       { "sqlite3_memdebug_settitle",  test_memdebug_settitle        ,0 },
       { "sqlite3_memdebug_malloc_count", test_memdebug_malloc_count ,0 },
       { "sqlite3_memdebug_log",       test_memdebug_log             ,0 },
       { "sqlite3_config_pagecache",   test_config_pagecache         ,0 },
       { "sqlite3_config_alt_pcache",  test_alt_pcache               ,0 },
       { "sqlite3_status",             test_status                   ,0 },
       { "sqlite3_db_status",          test_db_status                ,0 },
       { "install_malloc_faultsim",    test_install_malloc_faultsim  ,0 },
       { "sqlite3_config_heap",        test_config_heap              ,0 },
       { "sqlite3_config_heap_size",   test_config_heap_size         ,0 },
       { "sqlite3_config_memstatus",   test_config_memstatus         ,0 },
       { "sqlite3_config_lookaside",   test_config_lookaside         ,0 },
       { "sqlite3_config_error",       test_config_error             ,0 },
       { "sqlite3_config_uri",         test_config_uri               ,0 },
       { "sqlite3_config_cis",         test_config_cis               ,0 },
       { "sqlite3_config_pmasz",       test_config_pmasz             ,0 },
       { "sqlite3_db_config_lookaside",test_db_config_lookaside      ,0 },
       { "sqlite3_dump_memsys3",       test_dump_memsys3             ,3 },
       { "sqlite3_dump_memsys5",       test_dump_memsys3             ,5 },
       { "sqlite3_install_memsys3",    test_install_memsys3          ,0 },
       { "sqlite3_memdebug_vfs_oom_test", test_vfs_oom_test          ,0 },
    };
    int i;
    for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
      ClientData c = (ClientData)SQLITE_INT_TO_PTR(aObjCmd[i].clientData);
      Tcl_CreateObjCommand(interp, aObjCmd[i].zName, aObjCmd[i].xProc, c, 0);
    }
    return TCL_OK;
  }
  #endif
  