[comp.sources.atari.st] v01i061: varlint -- check C code for basic errors

koreth@ssyx.ucsc.edu (Steven Grimm) (07/06/88)

Submitted-by: decwrl!pacbell!dssch1!dsuthers
Posting-number: Volume 1, Issue 61
Archive-name: varlint

The following is a utility for those of us who program in 'C', but
are lousey typists.  The program checks for: 
scanf   sccanf   fscanf  printf   sprintf   fprintf
function calls, and checks their syntax.  It is a SUPPLEMENT to lint
(or a good syntax checking compiler); It is not a replacement for lint.

[Executable is being posted to the binaries group. -sg]

#--------------------------Cut Here--------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before the "#! /bin/sh" line,
# then unpack it by saving it in a file and typing "sh file."
#
# Wrapped by Steven Grimm (koreth) at ssyx on Wed Jul  6 07:38:20 1988
#
# unpacks with default permissions
#
# Contents : VARLINT.DOC VARLINT.C
#
if `test ! -s VARLINT.DOC`
then
echo "x - VARLINT.DOC"
cat > VARLINT.DOC << '@\Rogue\Monster\'
VARLINT(Local)			'C' Utility		VARLINT(Local)

SYNOPSIS
   varlint <program name> ... <program name>

HISTORY

   This program was developed under Lattice C on an Atari 520 ST. It
   is compatable with (and tested on) DEC 11/70 and AT&T 3B5 processors,
   running SYS V, and earlier versions too.  There is no reason that it
   won't compile on any system.

USAGE
   
   This program is designed to assist when coding in C.  It double checks
   your syntax for those funtions that have a variable number of arguments,
   and use the same syntax as printf / scanf.

WHEN TO USE IT
   
   If your program passes lint, and it compiles ok, but crashes any-way,
   or doesn't quite work right, try varlint.

   Mistakes with scanf or printf are one of the most frequent causes of
   intermittant crashes.  "Lint" does not check the syntax, and neither
   do most of the compilers.

CAPABILITIES:
   
   This program will detect and print out the following conditions.

	1)   More arguments following the control string than there are
	     percent signs in the control string.
	2)   Too many arguments as compared to the control string.
	3)   Mis-matches between the argument type and the item in
	     the control string.
	4)   Null control string.
	5)   Unknown specifiers after a percent sign in a control string.
        6)   Missing ampersands (&) in front of numbers. (scan functions)
	7)   Will not complain about valid uses. (allows funtion calls as
	     arguments, strings as *chars, etc.

RESTRICTIONS:
	
	1)   It does not yet check to ensure that the variables you name
	     are indeed the type you claim.
	2)   The use of a character array is allowed by the scanf / printf
	     functions, but is not yet verified by the program.  You
	     are notified of "un-recognized contstruct" in these cases.

LICENSE:

        Copyright 1988 by Daniel B. Suthers, Concord, California, USA.
        License is granted to distribute this program at will, as long as
        the source is also provided free of charge to anyone who 
        receives the binaries, or any one who wants it,
        AND all copyright notices are left intact.


@\Rogue\Monster\
else
  echo "shar: Will not over write VARLINT.DOC"
fi
if `test ! -s VARLINT.C`
then
echo "x - VARLINT.C"
cat > VARLINT.C << '@\Rogue\Monster\'
/*###########################################################################
 *
 *	This program is used to find those pesty errors in varags type
 *	functions such as printf.  It only handles those functions defined
 *	in the structure"fu".
 *
 *      By Daniel B. Suthers Mar 3, 1988
 *
 *      Copyright 1988 by Daniel B. Suthers, Concord, California, USA.
 *      License is granted to distribute this program at will, as long as
 *      the source is also provided free of charge to anyone who recieves
 *      the binaries, or to anyone who wants it,
 *	AND all copyright notices are left intact.
 *	The 
 *
###move $This_File 755 uprog none /uprog/bin/src/varlint.c
##move $This_File 755 uprog none /usr/uprog/src/bin/varlint.c
 *#########################################################################*/
#include <stdio.h>
#include <ctype.h>

#define VER "2.7"
#define MAX 512
#define NUMNAMES 6

#ifdef pdp11
char APPL[] ="\n##move $This_File 755 uprog none /usr/uprog/bin/varlint\n";
#else
char APPL[]="\n###move $This_File 755 uprog none /usr/lbin/varlint\n";
FILE *fopen();
void scandoos();
#endif

struct funcdat{
	char nam[9];				/* stores function names*/
	int len;				/* number of chars in names*/
	int pos;				/* position of control string*/
	int scan;
	};

struct funcdat fu[ NUMNAMES] = {
	{ "printf", 6, 0, 0},
	{ "scanf",  5, 0, 1},
	{ "sprintf", 7,1, 0},
	{ "sscanf", 6, 1, 1},
	{ "fprintf", 7,1, 0},
	{ "fscanf", 6, 1, 1}
	};

char *fgets(), *findname();
int in_comment = 0;				/* flag = 1 when in a comment*/

main(argc, argv, env)
int argc;
char *argv[], *env[];
{
  extern int optind;
  extern char *optarg;
  register int inta;
  char filename[128];
  FILE *infile ;

  while( (inta = getopt( argc, argv, "vVhH")) != EOF)
  {
    switch (inta)
    {
      case 'V':
      case 'v':
	fprintf (stderr, "%s\n"
	, VER);
	return(0);
      case 'h':
      case 'H':
	help();
	return(0);
      default:
	printf("Bad argument\nTry -h for help.\n");
	return(1);
    }
  }
  while( optind < argc )                        /* these would be files*/
  {
    strcpy(filename, argv[optind++]);
    if( (infile = fopen(filename, "r") ) == NULL)
    {
      fprintf(stderr, "Unable to open source file %s\n", filename);
    }
    else
    {
      printf("Working on %s\n", filename);
      scandoos(infile);
      if( infile != (FILE *) NULL)
	fclose(infile);
    }
  }
return(0);
}						/*end of main*/

help()
{
  printf(
    "\n\tCopyright 1988 by Daniel B. Suthers, Concord, California, USA.\n");
  printf(
    "\tLicense is granted to distribute this program at will, as long as\n");
  printf(
    "\tthe source is also provided free of charge to anyone who recieves\n");
  printf("\tthe binaries, or to anyone who wants it,\n");
  printf("\tAND all copyright notices are left intact.\n");
  printf("\n\tThis program checks for mismatched arguments in functions\n");
  printf("\tthat accept variable arguments ala printf().\n");
  printf("\tCurrently supports: printf, scanf and their cousins.\n");
  printf("\t(Checks only for # of args and & for integers)\n");
  printf("\n\tThe arguments accepted are:\n\n");
  printf("\t-v\tPrint the version number and exit.\n");
  printf("\t-help\tPrint the most current help.\n");
return(0);
}

#ifdef u3b5
void
#endif
scandoos(infile)
FILE *infile;
{
  register int linenum, reterr, oldline;
  char buff[MAX*3], *buffp;			/* allow 10 lines */
  char tempstr[MAX*3];
  int funcindex;
  
  linenum = oldline = 0;
  reterr = 0;
  while( fgets(buff, MAX, infile) != (char *)NULL )
  {
    if(reterr == 100 )				/* multi line statement */
    {
      if( oldline == 0)
        oldline = linenum;
      strcat(tempstr,buff);
      strcpy(buff, tempstr);
      reterr = 0;
    }
    linenum++;
    if( (buffp = findname(buff,&funcindex)) == NULL )
      continue;
    if( in_comment )
      continue;
    /*
    printf("%4d: %s",linenum, buffp);
    */
    if( (reterr = parsedo(buffp,funcindex)) > 0)
    {
      if( reterr < 100 )
      {
	if( oldline != 0)
	{
	  printf("***** Error at line %5d\n%s", oldline, buffp);
	  oldline = 0;
	}
	else
	  printf("***** Error at line %5d\n%s", linenum, buffp);
      }
      switch(reterr)
      {
	case 1:
	  printf("More \"%%\" in control string than there are variables.\n\n");
	  break;
	case 2:
	  printf("A varible type did not match the \"%%\".\n\n");
	  break;
	case 3:
	  printf("An undefined variable used.\n\n");
	  break;
	case 4: 
	  printf("An un-recognized construct, might be ok.\n\n");
	  break;
	case 5:
	  printf("Fewer \"%%\" in control string than there are variables.\n\n");
	  break;
	case 6:
	  printf("Integer type requires an ampersand (\&).\n\n");
	  break;
	case 7:
	  printf("Print found a string, expecting an int.\n\n");
	  break;
	case 8:
	  printf("Unknown %% specifier.\n\n");
	  break;
	case 9:
	  printf("Re declaration of the function. You sure this is ok?\n\n");
	  break;
	case 10:
	  printf("Null control string. You sure this is ok?\n\n");
	  break;
	case 100:
	  strcpy( tempstr, buff);
	  break;
      }
    }    					/* end of if(parsedo) */
    else
      oldline = 0;
  }     
}						/* end of scandoos() */

char *
findname(string, funcind)
char string[];
int *funcind;
{
  char *ptr;
  register int a;

  ptr = string;
  while( *ptr != NULL )
  {
    if( *ptr == '/')				/* set flag if this */
      if( *(ptr + 1) == '*')			/* is start of comment*/
      {
	in_comment = 1;
	ptr += 2;
	continue;
      }
    if( *ptr == '*' )				/* un-set flag if end */
      if( *(ptr+1) == '/')				/* of comment         */
      {
	in_comment = 0;
	ptr +=2;
	continue;
      }
    for( a = 0; a < NUMNAMES; a++)
    {
      if( strncmp( ptr, fu[a].nam, fu[a].len ) == 0 )
      {
	*funcind = a;
	return(ptr);
      }
    }
    ptr++;
  }
return(NULL);
}

int
parsedo(buff, fi)
char buff[];					/* function string */
int fi;						/* fu.[] index */
{
  register int parens, pcntnum, c;
  char *buffp;
  int end=0, inquotes = 0;
  int vartype[124];				/* for var type */

  parens = 0;
  buffp = buff;
  buffp += fu[fi].len; 				/* get past function */
  while( isspace(*buffp) )			/* skip white space  */
    buffp++;
  if( *buffp == NULL)
    return(100);
  if( *buffp != '(' )				/* not a function */
    return(0);
  if( *(buffp +1) == ')' )			/* function definition */
    return(9);
  buffp++;
  while( isspace(*buffp) )			/* skip white space  */
    buffp++;
  if( *buffp == NULL)
    return(100);
  for(c = 0;c < fu[fi].pos; c++)		/* skip to control string*/
  {						      /* (stop on comma) */
    while( (*buffp != NULL) && (*buffp != ',') )
	buffp++;
    if( *buffp == NULL)
      return(100);
    buffp++;
  }
  if( *buffp == NULL)
    return(100);
  while( isspace( *(buffp)) ) 		/* for ' , "test"'   */
    buffp++;
  if( *buffp == NULL)
    return(100);
  if( *buffp != '"')				/* should be on first quote*/
    return(4);
  if( *(buffp+1) == '"')			/* dont want "" as cntl str */
    return(10);
						/* count the percent signs */
						/* starting on first quote */
  pcntnum = 0;
  end = 0;
  while( *buffp++ != NULL )
  {
    switch(*buffp)
    {
	case '"':
	  if( *(buffp-1) != '\\')		/*    \" is not end   */
	    end = 1;
	  else if( *(buffp-2) == '\\')		/*    \\"  is end     */
	    end = 1;
	  break;
	case '%':
	  if( *(buffp-1) != '%')
	    pcntnum++;
	  else
	  {
	    pcntnum--;				/* delete first % of pair */
	    continue;
	  }
	  if( *(buffp+1) == '*' )
	  {
	    if(  fu[fi].scan == 1)
	    {
	      pcntnum--;			/* no matching variable */ 
	    }
	    else
	    {
	      pcntnum++;
	    }
	  }
						/*######################
						# load array with var type
						######################*/
						/* throw away justification*/
	  if( *(buffp +1 ) == '-' || *(buffp +1) == '+')
	    buffp++;
	  					/* remove blanks and sharps */
          while( isspace( *(buffp + 1)) || *(buffp + 1) == '#' ) 
            buffp++;
						/* throw away size spec */
	  while( isdigit(*(buffp+1)) || *(buffp+1) == '.' )
	    buffp++;
	  switch (*(buffp + 1) )		/* look at next char for type */
	  {
	    case 's':
	    case '[':
	    case 'c':
	      vartype[pcntnum] = 's';
	      break;
	    case 'd':
	    case 'l':
	    case 'h':
	    case 'i':
	    case 'o':
	    case 'x':
	    case 'u':
	    case 'f':
	    case 'e':
	    case 'g':
	      vartype[pcntnum] = 'i';
	      break;
	    case '%':				/* no action */
	    case '*':
	      break;
	    default:
	      printf("error at %s\n\n", buffp + 1);
	      return(8);
	      break;
	  }
	  break;
    }
    if( end )
	break;
  }
  parens = 0;
  inquotes = 1;					/* start on last " */
  c = 1;
  while( *buffp != NULL )			/* look for variables */
  {
    switch( *buffp )
    {
      case '(':
	  parens--;
	  break;
	case ')':
	  parens++;
	  break;
	case '"':				/* for strings */
	  if( parens == 0)			    /* at the base level */
	  {
	    if( *(buffp-1) != '\\' )		/*    \" is not end of str */
	    {
	      inquotes = !inquotes;
	    }
	    else if( *(buffp-2) == '\\')		/*    \\"  is end     */
	      inquotes = !inquotes;
	  }
	  break;
	case ',':
	  if( parens == 0 && inquotes != 1)	/* commas outside of func*/
	  {
	    pcntnum --;
	    buffp++;
	    while( isspace(*buffp) )
	      buffp++;
            if( *buffp == NULL)
              return(100);
	    if( fu[fi].scan == 1 )
	    {
	      if( vartype[c] == 'i' && *buffp != '\&' )
		return(6);
	    }
	    else if( vartype[c] == 'i' && *buffp == '"')
	        return(7);
	  c++;
	  continue;				/* buffp already ++ */
	  }
	  break;
    }
    if( parens == 1)				/* hit end of function */
    {
	if( pcntnum > 0 )
	  return(1);
	if( pcntnum < 0)
	  return(5);
	return(0);
    }
    buffp++;
  }
  if( parens < 1 )
    return(100);
return(0);
}						/* end of parsedo() */


@\Rogue\Monster\
else
  echo "shar: Will not over write VARLINT.C"
fi
# to concatenate archives, remove anything after this line
exit 0