159 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
** 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;
 | 
						|
}
 | 
						|
 | 
						|
/* Change 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; 
 | 
						|
}
 |