[comp.lang.c] Changes to BSD lint to check printf format and arguments.

rbutterworth@watmath.waterloo.edu (Ray Butterworth) (02/23/88)

Here are the changes I recently made to our version of BSD 4.3 lint.
Lines beginning with " +" are new and to be added, those with "- "
are old and to be deleted, and others are for context.

Note that it won't do much if your source doesn't bother to
include stdio.h.

============ /usr/include/stdio.h ============
+ extern int  printf(/*FORMAT1*/);
+ extern int fprintf(/*FORMAT2*/);
+ extern int sprintf(/*FORMAT2*/);    /* (char *) on BSD 4.3 */

============ /usr/src/lib/mip/pass1.h ============
-         char        sflags;                /* flags, see below */
+         short       sflags;                /* flags, see below */

+ #define SFMT1    0400        /* function uses printf format in arg 1 */
+ #define SFMT2   01000        /* function uses printf format in arg 2 */
+                              /* SFMT1|SFMT2 function uses it in arg 3 */

============ /usr/src/lib/mip/cgram.y ============
fdeclarator:
        ...
        | name_lp RP
                = {
+ #ifdef LINT
+                        {extern int fmtflag;}
+                        if (fmtflag){
+                                if (fmtflag&01)
+                                        stab[$1].sflags |= SFMT1;
+                                if (fmtflag&02)
+                                        stab[$1].sflags |= SFMT2;
+                                fmtflag = 0;
+                         }
+ #endif /*LINT*/
                          $$ = bdty(UNARY CALL, ...

============ /usr/lib/mip/common.c ============
-    static char *tnames[] = {
+    char *tnames[] = {

============ /usr/lib/mip/scan.c ============
lxcom() {
      ...
      switch (c=getchar()) {
      ...
+     case 'F':
+ #       define FMTSIZE 6        /* strlen("FORMAT") */
+         {extern int fmtflag;}
+
+         lxget( c, LEXLET|LEXDIG );
+         if(strncmp(yytext, "FORMAT", FMTSIZE))
+                      continue;
+         fmtflag = 1;
+         if (yytext[FMTSIZE]) {
+                 fmtflag = yytext[FMTSIZE] - '0';
+                 if ((yytext[FMTSIZE+1] != '\0')
+                  || (fmtflag < 1)
+                  || (fmtflag > 3) ) {
+                         werror("number in \"%s\" must be 1, 2, or 3", yytext);
+                         fmtflag = 0;
+                 }
+         }
+         continue;

=== /usr/src/usr.bin/lint/macdefs.h ===
- # define getlab() 10

=== /usr/src/usr.bin/lint/lpass1.c ===
+ #define SAVECYCLE 64
+ static char *savestring[SAVECYCLE] = 0;
+ static int savecurrent = SAVECYCLE;
+ static int saveused = 1;
+ 
+ extern char *malloc();
+ extern char *realloc();
+ 
+ int fmtflag = 0;        /* following function has a printf-type format */

lprt( p, down, uses ) register NODE *p; {
    ...
    switch( p->in.op ){
        ...
        case UNARY CALL:
            ...
            outdef( sp, lty, acount );
            if( acount ) {
                lpta( p->in.right );
+               if (hflag
+                && (sp->sflags & (SFMT1|SFMT2))) {
+                   register int fmtarg = 0;
+                   if (sp->sflags & SFMT1)
+                       fmtarg += 1;
+                   if (sp->sflags & SFMT2)
+                       fmtarg += 2;
+                   check_format(p, fmtarg);
+               }
            ...
            }
        ...
    }
    ...
}



+     static char *
+ maketype(t)
+     TWORD t;
+ {
+     extern char *tnames[];
+     static char typename[256];
+ 
+     typename[0] = '\0';
+     for (; ; t = DECREF(t)) {
+         if (ISPTR(t))
+             strcat(typename, "pointer to ");
+         else if (ISFTN(t))
+             strcat(typename, "function returning ");
+         else if (ISARY(t))
+             strcat(typename, "array of ");
+         else {
+             strcat(typename, tnames[t]);
+             return typename;
+         }
+     }
+ }
+ 
+ getlab()
+ {
+     if (saveused) {
+         saveused = 0;
+         if (--savecurrent == 0)    /* Don't use 0.  We have to negate it. */
+             savecurrent = SAVECYCLE-1;
+     }
+     return savecurrent;
+ }
+ 

-     /*ARGSUSED*/
- bycode(t,i){;}

+ bycode(t,i)
+ {
+     static int savesize[SAVECYCLE];
+ 
+     saveused = 1;
+ 
+     if (i >= savesize[savecurrent]) {
+         while (i >= savesize[savecurrent])
+             savesize[savecurrent] = (savesize[savecurrent]*2) + 4;
+         savestring[savecurrent]
+             = realloc(savestring[savecurrent], savesize[savecurrent]);
+     }
+ 
+     savestring[savecurrent][i] = t;
+ }
+ 
+     static char *
+ makeformat(number, start, end)
+     int number;
+     char *start;
+     char *end;
+ {
+     static char *format = 0;
+     static int avail = 0;
+     auto int size;
+ 
+     size = end - start + 20;
+     if (size >= avail) {
+         if (format)
+             free(format);
+         while (avail < size)
+             avail = (avail*2) + 4;
+         format = malloc(avail);
+     }
+     sprintf(format, "#%d \"%.*s\"", number, end-start+1, start);
+     return format;
+ }
+ 
+     static TWORD
+ promote(type)
+     TWORD type;
+ {
+     switch (type) {
+         case FLOAT:
+         case DOUBLE:
+             return DOUBLE;
+ 
+         case LONG:
+         case ULONG:
+             return LONG;
+ 
+         case CHAR:
+         case UCHAR:
+         case SHORT:
+         case USHORT:
+         case INT:
+         case UNSIGNED:
+         case ENUMTY:
+         case MOETY:
+             return INT;
+     }
+ 
+     return type;
+ }
+ 
+ #define MAXARGS 64
+ 
+ #define push(p) \
+     if (top < MAXARGS) arg_stack[top++] = p; \
+     else goto overflow;
+ 
+ #define pop(p) \
+     if (top > 0) (p) = arg_stack[--top]; \
+     else goto underflow;
+         
+ static NODE *arg_stack[MAXARGS];
+ 
+ static int top;
+ 
+ check_format(p, fmtarg)
+     register NODE *p;
+     int fmtarg;
+ {
+     register char *format;
+     auto char *start;
+     auto char *func_name = "printf-like function";
+     auto int fmtnum;
+ 
+     if (p->in.left->tn.op == ICON)
+         func_name = stab[p->in.left->tn.rval].sname;
+ 
+     /* save all the arguments */
+     top = 0;
+     p = p->in.right;
+     while (p->in.op == CM) {
+         push(p->in.right);
+         p = p->in.left;
+     }
+     push(p);
+ 
+     /* get the format string */
+     while (--fmtarg >= 0)
+         pop(p);
+     if ((p->in.type != (PTR|CHAR))
+      || (p->in.op != ICON)
+      || (p->tn.rval >= 0)) {
+         if (Oflag) {
+             werror("function '%s': format and argument types not linted",
+                 func_name);
+         }
+         return;
+     }
+     format = savestring[-p->tn.rval];
+ 
+     fmtnum = 0;
+     while (*format) {
+         if (*format == '%') {
+             start = format;
+             if (*++format != '%') {
+                 auto TWORD scale = INT;
+                 auto TWORD type;
+ 
+                 ++fmtnum;
+                 if (*format == '#') ++format;
+                 if ((*format == '+')
+                  || (*format == '-')) ++format;
+                 if (*format == '*') {
+                     ++format;
+                     pop(p);
+                     type = promote(p->in.type);
+                     if (type != INT)
+                         werror("format %s *-width requires int, not %s",
+                             makeformat(fmtnum, start, format+1),
+                             maketype(type));
+                 }
+                 else while(isdigit(*format)) ++format;
+                 if (*format == '.') {
+                     ++format;
+                     if (*format == '*') {
+                         ++format;
+                         pop(p);
+                         type = promote(p->in.type);
+                         if (type != INT)
+                             werror("format %s *-precision requires int, not %s",
+                                 makeformat(fmtnum, start, format+1),
+                                 maketype(type));
+                     }
+                     else while(isdigit(*format)) ++format;
+                 }
+                 if (*format == 'h') {
+                     ++format;
+                     scale = SHORT;
+                 }
+                 else if (*format == 'l') {
+                     ++format;
+                     scale = LONG;
+                 }
+                 pop(p);
+                 type = promote(p->in.type);
+                 switch (*format) {
+                     default:
+                         werror("unknown %s format specifier",
+                             makeformat(fmtnum, start, format));
+                         break;
+                     case 's':
+                         if (((PTR|CHAR) != type)
+                          && ((PTR|UCHAR) != type))
+                             werror("format %s requires string, not %s",
+                                 makeformat(fmtnum, start, format),
+                                 maketype(type));
+                         break;
+                     case 'e':
+                     case 'E':
+                     case 'f':
+                     case 'g':
+                     case 'G':
+                         if (DOUBLE != type)
+                             werror("format %s requires double, not %s",
+                                 makeformat(fmtnum, start, format),
+                                 maketype(type));
+                         break;
+                     case 'p':
+                         /* BSD 4.3 has neither (void *) nor %p,
+                          * but we can check for it anyway.
+                          */
+                         if (type != PTR)
+                             werror("format %s requires (void *), not %s",
+                                 makeformat(fmtnum, start, format),
+                                 maketype(type));
+                         break;
+                     case 'd':
+                     case 'c':
+                     case 'u':
+                     case 'o':
+                     case 'x':
+                     case 'X':
+                         if (scale == LONG) {
+                             if (LONG != type)
+                                 werror("format %s requires long, not %s",
+                                     makeformat(fmtnum, start, format),
+                                     maketype(type));
+                         }
+                         else {
+                             if ((INT != type)
+                              && (!ISPTR(type)))  /* all ptrs are int (ugh!) */
+                                 werror("format %s requires int, not %s",
+                                     makeformat(fmtnum, start, format),
+                                     maketype(type));
+                         }
+                         scale = INT;
+                         break;
+                 }
+                 if (scale != INT)
+                     werror("format %s scale 'h' or 'l' is inapproprate for %s",
+                         makeformat(fmtnum, start, format),
+                         maketype(type));
+             }
+         }
+         ++format;
+     }
+ 
+     if (top != 0)
+         werror("Too many arguments for format");
+ 
+     return;
+ 
+     overflow:
+         werror("lint error:  only first %d arguments checked", MAXARGS);
+         return;
+ 
+     underflow:
+         werror("Not enough arguments for format");
+         return;
+ }

============ That's all there is ============