1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
/*
** 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;
}
|