/* ** 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. ** ** $Id: test_malloc.c,v 1.19 2008/03/25 09:47:35 danielk1977 Exp $ */ #include "sqliteInt.h" #include "tcl.h" #include #include #include /* ** 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( 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>= 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; isizeof(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; i0 ){ 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 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 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 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 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 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 ** -benigncnt ** ** 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 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; ii1 && 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; } } sqlite3_test_control(-12345); /* Just to stress the test_control interface */ nBenign = sqlite3_test_control(SQLITE_TESTCTRL_FAULT_BENIGN_FAILURES, SQLITE_FAULTINJECTOR_MALLOC); nFail = sqlite3_test_control(SQLITE_TESTCTRL_FAULT_FAILURES, SQLITE_FAULTINJECTOR_MALLOC); sqlite3_test_control(SQLITE_TESTCTRL_FAULT_CONFIG, SQLITE_FAULTINJECTOR_MALLOC, 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 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 = sqlite3_test_control(SQLITE_TESTCTRL_FAULT_PENDING, SQLITE_FAULTINJECTOR_MALLOC); Tcl_SetObjResult(interp, Tcl_NewIntObj(nPending)); return TCL_OK; } /* ** 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 test_memdebug_settitle( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ const char *zTitle; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "TITLE"); return TCL_ERROR; } zTitle = Tcl_GetString(objv[1]); #ifdef SQLITE_MEMDEBUG { extern int sqlite3MemdebugSettitle(const char*); sqlite3MemdebugSettitle(zTitle); } #endif return TCL_OK; } #define MALLOC_LOG_FRAMES 10 static Tcl_HashTable aMallocLog; static int mallocLogEnabled = 0; typedef struct MallocLog MallocLog; struct MallocLog { int nCall; int nByte; }; static void test_memdebug_callback(int nByte, int nFrame, void **aFrame){ if( mallocLogEnabled ){ MallocLog *pLog; Tcl_HashEntry *pEntry; int isNew; int aKey[MALLOC_LOG_FRAMES]; int nKey = sizeof(int)*MALLOC_LOG_FRAMES; memset(aKey, 0, nKey); if( (sizeof(void*)*nFrame)nCall++; pLog->nByte += nByte; } } static int test_memdebug_log( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ static int isInit = 0; int iSub; enum MB_enum { MB_LOG_START, MB_LOG_STOP, MB_LOG_DUMP, MB_LOG_CLEAR }; static const char *MB_strs[] = { "start", "stop", "dump", "clear" }; if( !isInit ){ #ifdef SQLITE_MEMDEBUG extern void sqlite3MemdebugBacktraceCallback( void (*xBacktrace)(int, int, void **)); sqlite3MemdebugBacktraceCallback(test_memdebug_callback); #endif Tcl_InitHashTable(&aMallocLog, MALLOC_LOG_FRAMES); 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(int)==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); int *aKey = (int *)Tcl_GetHashKey(&aMallocLog, pEntry); int ii; apElem[0] = Tcl_NewIntObj(pLog->nCall); apElem[1] = Tcl_NewIntObj(pLog->nByte); for(ii=0; ii