summaryrefslogtreecommitdiffstats
path: root/tool/srcck1.c
blob: 20084ac47f1a0793c2bd9f6c511ab7da0e34e544 (plain)
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; 
}