Add a utility program that looks for assert(), NEVER(), ALWAYS(), and

testcase() macros that have side-effects, and reports errors when they are
found.  Also fix a bug that this utility detected as it was being tested.

FossilOrigin-Name: b0b4624fc5d53bb0cc9fae7dad51984837d946ac
This commit is contained in:
drh 2016-02-06 22:32:06 +00:00
parent f5818aa560
commit cc5f8a46b9
7 changed files with 192 additions and 19 deletions

View File

@ -583,6 +583,12 @@ sqlite3$(TEXE): $(TOP)/src/shell.c libsqlite3.la sqlite3.h
sqldiff$(TEXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(TOP)/tool/sqldiff.c sqlite3.c $(TLIBS)
srcck1$(BEXE): $(TOP)/tool/srcck1.c
$(BCC) -o srcck1$(BEXE) $(TOP)/tool/srcck1.c
sourcetest: srcck1$(BEXE) sqlite3.c
./srcck1 sqlite3.c
fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(FUZZERSHELL_OPT) \
$(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS)
@ -1083,7 +1089,7 @@ quicktest: ./testfixture$(TEXE)
# This is the common case. Run many tests that do not take too long,
# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
#
test: $(TESTPROGS) fastfuzztest
test: $(TESTPROGS) sourcetest fastfuzztest
./testfixture$(TEXE) $(TOP)/test/veryquick.test $(TESTOPTS)
# Run a test using valgrind. This can take a really long time

View File

@ -480,6 +480,12 @@ sqldiff$(EXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
$(TCCX) -o sqldiff$(EXE) -DSQLITE_THREADSAFE=0 \
$(TOP)/tool/sqldiff.c sqlite3.c $(TLIBS) $(THREADLIB)
srcck1$(EXE): $(TOP)/tool/srcck1.c
$(BCC) -o srcck1$(EXE) $(TOP)/tool/srcck1.c
sourcetest: srcck1$(EXE) sqlite3.c
./srcck1 sqlite3.c
fuzzershell$(EXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
$(TCCX) -o fuzzershell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
$(FUZZERSHELL_OPT) $(TOP)/tool/fuzzershell.c sqlite3.c \
@ -768,7 +774,7 @@ quicktest: ./testfixture$(EXE)
# The default test case. Runs most of the faster standard TCL tests,
# and fuzz tests, and sqlite3_analyzer and sqldiff tests.
#
test: $(TESTPROGS) fastfuzztest
test: $(TESTPROGS) sourcetest fastfuzztest
./testfixture$(EXE) $(TOP)/test/veryquick.test $(TESTOPTS)
# Run a test using valgrind. This can take a really long time

View File

@ -1,6 +1,6 @@
C Make\ssure\svariable\sdeclarations\soccur\sat\sthe\sbeginning\sof\sblocks,\seven\nwith\sSQLITE_DEBUG\senabled.
D 2016-02-06T19:48:50.321
F Makefile.in 027c1603f255390c43a426671055a31c0a65fdb4
C Add\sa\sutility\sprogram\sthat\slooks\sfor\sassert(),\sNEVER(),\sALWAYS(),\sand\ntestcase()\smacros\sthat\shave\sside-effects,\sand\sreports\serrors\swhen\sthey\sare\nfound.\s\sAlso\sfix\sa\sbug\sthat\sthis\sutility\sdetected\sas\sit\swas\sbeing\stested.
D 2016-02-06T22:32:06.228
F Makefile.in 0a957a57243a3d55e96b1514e22ffae5db9ea116
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc 72b7858f02017611c3ac1ddc965251017fed0845
F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
@ -272,7 +272,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 960071a0bceb043bc5627573986154f507931f33
F main.mk f51c0652d2a623160e90a758e01312a6a00f3454
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@ -291,7 +291,7 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
F src/backup.c 2869a76c03eb393ee795416e2387005553df72bc
F src/bitvec.c 1a78d450a17c5016710eec900bedfc5729bf9bdf
F src/btmutex.c bc87dd3b062cc26edfe79918de2200ccb8d41e73
F src/btree.c 0b359bcc2316a57acf12f583253974ad22b4654f
F src/btree.c 4c8caaeed7878aafdb607c3d2bcbc365bb0d19a1
F src/btree.h 368ceeb4bd9312dc8df2ffd64b4b7dbcf4db5f8e
F src/btreeInt.h c18b7d2a3494695133e4e60ee36061d37f45d9a5
F src/build.c 198eaa849c193f28b802ed135b2483c68ef7a35c
@ -313,7 +313,7 @@ F src/insert.c b84359365bace233919db550a15f131923190efc
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c b1b0880fc474abfab89e737b0ecfde0bd7a60902
F src/loadext.c 84996d7d70a605597d79c1f1d7b2012a5fd34f2b
F src/main.c 62b7fe3ed245757d1ff2e6268a7ec0bc30100308
F src/main.c b67a45397b93b7ba8fbd6bfcb03423d245baed05
F src/malloc.c 337e9808b5231855fe28857950f4f60ae42c417f
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c 6919bcf12f221868ea066eec27e579fed95ce98b
@ -1416,6 +1416,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
F tool/sqldiff.c 5a26205111e6fa856d9b1535b1637744dcdb930b
F tool/srcck1.c 3119733530abcef14f1b0603c66207a342936263
F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
F tool/symbols.sh fec58532668296d7c7dc48be9c87f75ccdb5814f
@ -1426,7 +1427,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P a2952231ac7abe165ed070875728f752ae0be608
R da8a8c4dec6af3be8a6f4cff524d4f5f
P 2f7778e64d93ef237e23ceac01ea9808df5cf2a1
R e75f025e263d6165f7cd0bcd65f5ad12
U drh
Z f3b1e3c58e3e9ab1cb32cfa377da1181
Z 663376736f9ceb33b8ce1b1cb94df97c

View File

@ -1 +1 @@
2f7778e64d93ef237e23ceac01ea9808df5cf2a1
b0b4624fc5d53bb0cc9fae7dad51984837d946ac

View File

@ -6148,7 +6148,7 @@ static int fillInCell(
{
CellInfo info;
pPage->xParseCell(pPage, pCell, &info);
assert( nHeader=(int)(info.pPayload - pCell) );
assert( nHeader==(int)(info.pPayload - pCell) );
assert( info.nKey==nKey );
assert( *pnSize == info.nSize );
assert( spaceLeft == info.nLocal );
@ -7807,8 +7807,8 @@ static int balance(BtCursor *pCur){
u8 aBalanceQuickSpace[13];
u8 *pFree = 0;
TESTONLY( int balance_quick_called = 0 );
TESTONLY( int balance_deeper_called = 0 );
VVA_ONLY( int balance_quick_called = 0 );
VVA_ONLY( int balance_deeper_called = 0 );
do {
int iPage = pCur->iPage;
@ -7821,7 +7821,8 @@ static int balance(BtCursor *pCur){
** and copy the current contents of the root-page to it. The
** next iteration of the do-loop will balance the child page.
*/
assert( (balance_deeper_called++)==0 );
assert( balance_deeper_called==0 );
VVA_ONLY( balance_deeper_called++ );
rc = balance_deeper(pPage, &pCur->apPage[1]);
if( rc==SQLITE_OK ){
pCur->iPage = 1;
@ -7860,7 +7861,8 @@ static int balance(BtCursor *pCur){
** function. If this were not verified, a subtle bug involving reuse
** of the aBalanceQuickSpace[] might sneak in.
*/
assert( (balance_quick_called++)==0 );
assert( balance_quick_called==0 );
VVA_ONLY( balance_quick_called++ );
rc = balance_quick(pParent, pPage, aBalanceQuickSpace);
}else
#endif
@ -9327,7 +9329,8 @@ char *sqlite3BtreeIntegrityCheck(
sqlite3BtreeEnter(p);
assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE );
assert( (nRef = sqlite3PagerRefcount(pBt->pPager))>=0 );
VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) );
assert( nRef>=0 );
sCheck.pBt = pBt;
sCheck.pPager = pBt->pPager;
sCheck.nPage = btreePagecount(sCheck.pBt);

View File

@ -3566,7 +3566,7 @@ int sqlite3_test_control(int op, ...){
*/
case SQLITE_TESTCTRL_ASSERT: {
volatile int x = 0;
assert( (x = va_arg(ap,int))!=0 );
assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 );
rc = x;
break;
}

157
tool/srcck1.c Normal file
View File

@ -0,0 +1,157 @@
/*
** The program does some simple static analysis of the sqlite3.c source
** file looking for mistakes.
**
** Usage:
**
** ./srcck1 sqlite3.c
**
** This program looks for instances of assert(), ALWAYS(), NEVER() or
** testcase() that contain side-effects and reports errors if any such
** instances are found.
**
** The aim of this utility is to prevent recurrences of errors such
** as the one fixed at:
**
** https://www.sqlite.org/src/info/a2952231ac7abe16
**
** Note that another similar error was found by this utility when it was
** first written. That other error was fixed by the same check-in that
** committed the first version of this utility program.
*/
#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
/* Read the complete text of a file into memory. Return a pointer to
** the result. Panic if unable to read the file or allocate memory.
*/
static char *readFile(const char *zFilename){
FILE *in;
char *z;
long n;
size_t got;
in = fopen(zFilename, "rb");
if( in==0 ){
fprintf(stderr, "unable to open '%s' for reading\n", zFilename);
exit(1);
}
fseek(in, 0, SEEK_END);
n = ftell(in);
rewind(in);
z = malloc( n+1 );
if( z==0 ){
fprintf(stderr, "cannot allocate %d bytes to store '%s'\n",
(int)(n+1), zFilename);
exit(1);
}
got = fread(z, 1, n, in);
fclose(in);
if( got!=(size_t)n ){
fprintf(stderr, "only read %d of %d bytes from '%s'\n",
(int)got, (int)n, zFilename);
exit(1);
}
z[n] = 0;
return z;
}
/* Change the C code in the argument to see if it might have
** side effects. The only accurate way to know this is to do a full
** parse of the C code, which this routine does not do. This routine
** uses a simple heuristic of looking for:
**
** * '=' not immediately after '>', '<', '!', or '='.
** * '++'
** * '--'
**
** If the code contains the phrase "side-effects-ok" is inside a
** comment, then always return false. This is used to disable checking
** for assert()s with deliberate side-effects, such as used by
** SQLITE_TESTCTRL_ASSERT - a facility that allows applications to
** determine at runtime whether or not assert()s are enabled.
** Obviously, that determination cannot be made unless the assert()
** has some side-effect.
**
** Return true if a side effect is seen. Return false if not.
*/
static int hasSideEffect(const char *z, unsigned int n){
unsigned int i;
for(i=0; i<n; i++){
if( z[i]=='/' && strncmp(&z[i], "/*side-effects-ok*/", 19)==0 ) return 0;
if( z[i]=='=' && i>0 && z[i-1]!='=' && z[i-1]!='>'
&& z[i-1]!='<' && z[i-1]!='!' && z[i+1]!='=' ) return 1;
if( z[i]=='+' && z[i+1]=='+' ) return 1;
if( z[i]=='-' && z[i+1]=='-' ) return 1;
}
return 0;
}
/* Return the number of bytes in string z[] prior to the first unmatched ')'
** character.
*/
static unsigned int findCloseParen(const char *z){
unsigned int nOpen = 0;
unsigned i;
for(i=0; z[i]; i++){
if( z[i]=='(' ) nOpen++;
if( z[i]==')' ){
if( nOpen==0 ) break;
nOpen--;
}
}
return i;
}
/* Search for instances of assert(...), ALWAYS(...), NEVER(...), and/or
** testcase(...) where the argument contains side effects.
**
** Print error messages whenever a side effect is found. Return the number
** of problems seen.
*/
static unsigned int findAllSideEffects(const char *z){
unsigned int lineno = 1; /* Line number */
unsigned int i;
unsigned int nErr = 0;
char c, prevC = 0;
for(i=0; (c = z[i])!=0; prevC=c, i++){
if( c=='\n' ){ lineno++; continue; }
if( isalpha(c) && !isalpha(prevC) ){
if( strncmp(&z[i],"assert(",7)==0
|| strncmp(&z[i],"ALWAYS(",7)==0
|| strncmp(&z[i],"NEVER(",6)==0
|| strncmp(&z[i],"testcase(",9)==0
){
unsigned int j, n;
const char *z2 = &z[i+5];
while( z2[0]!='(' ){ z2++; }
z2++;
n = findCloseParen(z2);
if( hasSideEffect(z2, n) ){
nErr++;
fprintf(stderr, "side-effect line %u: %.*s\n", lineno,
(int)(&z2[n+1] - &z[i]), &z[i]);
}
}
}
}
return nErr;
}
int main(int argc, char **argv){
char *z;
unsigned int nErr = 0;
if( argc!=2 ){
fprintf(stderr, "Usage: %s FILENAME\n", argv[0]);
return 1;
}
z = readFile(argv[1]);
nErr = findAllSideEffects(z);
free(z);
if( nErr ){
fprintf(stderr, "Found %u undesirable side-effects\n", nErr);
return 1;
}
return 0;
}