drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 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> |
drh | fd0d93f | 2016-02-07 00:08:08 +0000 | [diff] [blame] | 25 | #include <string.h> |
drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 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 | |
drh | bd441f7 | 2016-07-25 02:31:48 +0000 | [diff] [blame] | 61 | /* Check the C code in the argument to see if it might have |
drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 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 | */ |
drh | cf7e755 | 2016-05-02 13:57:19 +0000 | [diff] [blame] | 114 | static unsigned int findAllSideEffects(const char *z){ |
drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 115 | unsigned int lineno = 1; /* Line number */ |
| 116 | unsigned int i; |
| 117 | unsigned int nErr = 0; |
drh | cf7e755 | 2016-05-02 13:57:19 +0000 | [diff] [blame] | 118 | char c, prevC = 0; |
drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 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 | ){ |
mistachkin | 27b6997 | 2016-02-07 20:39:27 +0000 | [diff] [blame] | 127 | unsigned int n; |
drh | cf7e755 | 2016-05-02 13:57:19 +0000 | [diff] [blame] | 128 | const char *z2 = &z[i+5]; |
drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 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){ |
drh | cf7e755 | 2016-05-02 13:57:19 +0000 | [diff] [blame] | 144 | char *z; |
drh | cc5f8a4 | 2016-02-06 22:32:06 +0000 | [diff] [blame] | 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 | } |