blob: 20084ac47f1a0793c2bd9f6c511ab7da0e34e544 [file] [log] [blame]
drhcc5f8a42016-02-06 22:32:06 +00001/*
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>
drhfd0d93f2016-02-07 00:08:08 +000025#include <string.h>
drhcc5f8a42016-02-06 22:32:06 +000026
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*/
30static 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
drhbd441f72016-07-25 02:31:48 +000061/* Check the C code in the argument to see if it might have
drhcc5f8a42016-02-06 22:32:06 +000062** 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*/
80static 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*/
95static 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*/
drhcf7e7552016-05-02 13:57:19 +0000114static unsigned int findAllSideEffects(const char *z){
drhcc5f8a42016-02-06 22:32:06 +0000115 unsigned int lineno = 1; /* Line number */
116 unsigned int i;
117 unsigned int nErr = 0;
drhcf7e7552016-05-02 13:57:19 +0000118 char c, prevC = 0;
drhcc5f8a42016-02-06 22:32:06 +0000119 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 ){
mistachkin27b69972016-02-07 20:39:27 +0000127 unsigned int n;
drhcf7e7552016-05-02 13:57:19 +0000128 const char *z2 = &z[i+5];
drhcc5f8a42016-02-06 22:32:06 +0000129 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
143int main(int argc, char **argv){
drhcf7e7552016-05-02 13:57:19 +0000144 char *z;
drhcc5f8a42016-02-06 22:32:06 +0000145 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}