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 */
readFile(const char * zFilename)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 */
hasSideEffect(const char * z,unsigned int n)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 */
findCloseParen(const char * z)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 */
findAllSideEffects(const char * z)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 
main(int argc,char ** argv)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