blob: 20084ac47f1a0793c2bd9f6c511ab7da0e34e544 [file] [log] [blame]
/*
** 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;
}