1 /* 2 ** The program does some simple static analysis of the sqlite3.c source 3 ** file looking for mistakes. 4 ** 5 ** Usage: 6 ** 7 ** ./srcck1 sqlite3.c 8 ** 9 ** This program looks for instances of assert(), ALWAYS(), NEVER() or 10 ** testcase() that contain side-effects and reports errors if any such 11 ** instances are found. 12 ** 13 ** The aim of this utility is to prevent recurrences of errors such 14 ** as the one fixed at: 15 ** 16 ** https://www.sqlite.org/src/info/a2952231ac7abe16 17 ** 18 ** Note that another similar error was found by this utility when it was 19 ** first written. That other error was fixed by the same check-in that 20 ** committed the first version of this utility program. 21 */ 22 #include <stdlib.h> 23 #include <ctype.h> 24 #include <stdio.h> 25 #include <string.h> 26 27 /* Read the complete text of a file into memory. Return a pointer to 28 ** the result. Panic if unable to read the file or allocate memory. 29 */ 30 static char *readFile(const char *zFilename){ 31 FILE *in; 32 char *z; 33 long n; 34 size_t got; 35 36 in = fopen(zFilename, "rb"); 37 if( in==0 ){ 38 fprintf(stderr, "unable to open '%s' for reading\n", zFilename); 39 exit(1); 40 } 41 fseek(in, 0, SEEK_END); 42 n = ftell(in); 43 rewind(in); 44 z = malloc( n+1 ); 45 if( z==0 ){ 46 fprintf(stderr, "cannot allocate %d bytes to store '%s'\n", 47 (int)(n+1), zFilename); 48 exit(1); 49 } 50 got = fread(z, 1, n, in); 51 fclose(in); 52 if( got!=(size_t)n ){ 53 fprintf(stderr, "only read %d of %d bytes from '%s'\n", 54 (int)got, (int)n, zFilename); 55 exit(1); 56 } 57 z[n] = 0; 58 return z; 59 } 60 61 /* Check the C code in the argument to see if it might have 62 ** side effects. The only accurate way to know this is to do a full 63 ** parse of the C code, which this routine does not do. This routine 64 ** uses a simple heuristic of looking for: 65 ** 66 ** * '=' not immediately after '>', '<', '!', or '='. 67 ** * '++' 68 ** * '--' 69 ** 70 ** If the code contains the phrase "side-effects-ok" is inside a 71 ** comment, then always return false. This is used to disable checking 72 ** for assert()s with deliberate side-effects, such as used by 73 ** SQLITE_TESTCTRL_ASSERT - a facility that allows applications to 74 ** determine at runtime whether or not assert()s are enabled. 75 ** Obviously, that determination cannot be made unless the assert() 76 ** has some side-effect. 77 ** 78 ** Return true if a side effect is seen. Return false if not. 79 */ 80 static int hasSideEffect(const char *z, unsigned int n){ 81 unsigned int i; 82 for(i=0; i<n; i++){ 83 if( z[i]=='/' && strncmp(&z[i], "/*side-effects-ok*/", 19)==0 ) return 0; 84 if( z[i]=='=' && i>0 && z[i-1]!='=' && z[i-1]!='>' 85 && z[i-1]!='<' && z[i-1]!='!' && z[i+1]!='=' ) return 1; 86 if( z[i]=='+' && z[i+1]=='+' ) return 1; 87 if( z[i]=='-' && z[i+1]=='-' ) return 1; 88 } 89 return 0; 90 } 91 92 /* Return the number of bytes in string z[] prior to the first unmatched ')' 93 ** character. 94 */ 95 static unsigned int findCloseParen(const char *z){ 96 unsigned int nOpen = 0; 97 unsigned i; 98 for(i=0; z[i]; i++){ 99 if( z[i]=='(' ) nOpen++; 100 if( z[i]==')' ){ 101 if( nOpen==0 ) break; 102 nOpen--; 103 } 104 } 105 return i; 106 } 107 108 /* Search for instances of assert(...), ALWAYS(...), NEVER(...), and/or 109 ** testcase(...) where the argument contains side effects. 110 ** 111 ** Print error messages whenever a side effect is found. Return the number 112 ** of problems seen. 113 */ 114 static unsigned int findAllSideEffects(const char *z){ 115 unsigned int lineno = 1; /* Line number */ 116 unsigned int i; 117 unsigned int nErr = 0; 118 char c, prevC = 0; 119 for(i=0; (c = z[i])!=0; prevC=c, i++){ 120 if( c=='\n' ){ lineno++; continue; } 121 if( isalpha(c) && !isalpha(prevC) ){ 122 if( strncmp(&z[i],"assert(",7)==0 123 || strncmp(&z[i],"ALWAYS(",7)==0 124 || strncmp(&z[i],"NEVER(",6)==0 125 || strncmp(&z[i],"testcase(",9)==0 126 ){ 127 unsigned int n; 128 const char *z2 = &z[i+5]; 129 while( z2[0]!='(' ){ z2++; } 130 z2++; 131 n = findCloseParen(z2); 132 if( hasSideEffect(z2, n) ){ 133 nErr++; 134 fprintf(stderr, "side-effect line %u: %.*s\n", lineno, 135 (int)(&z2[n+1] - &z[i]), &z[i]); 136 } 137 } 138 } 139 } 140 return nErr; 141 } 142 143 int main(int argc, char **argv){ 144 char *z; 145 unsigned int nErr = 0; 146 if( argc!=2 ){ 147 fprintf(stderr, "Usage: %s FILENAME\n", argv[0]); 148 return 1; 149 } 150 z = readFile(argv[1]); 151 nErr = findAllSideEffects(z); 152 free(z); 153 if( nErr ){ 154 fprintf(stderr, "Found %u undesirable side-effects\n", nErr); 155 return 1; 156 } 157 return 0; 158 } 159