159 lines
4.4 KiB
C
159 lines
4.4 KiB
C
/*
|
|
** 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>
|
|
#include <string.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;
|
|
}
|
|
|
|
/* Check 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 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;
|
|
}
|