[comp.lang.c] lint suggestion

ok@quintus.UUCP (Richard A. O'Keefe) (02/08/88)

There's a feature missing from every Lint I've tried, which would be
very useful, and should be easy to add (if I had sources, I'd be doing
it right now).  Consider the stdio functions
	fscanf( FILE *f, const char *f, ...)
	sscanf( char *b, const char *f, ...)
	 scanf(          const char *f, ...)
	fprintf(FILE *f, const char *f, ...)
	sprintf(char *b, const char *f, ...)
	 printf(         const char *f, ...)
In each of these functions, the format argument is almost always a literal,
and it is very easy by inspecting this literal to determine how many
arguments should follow and what their types should be.  Why not check
that the arguments are consistent with the format?  (This would also help
to catch unimplemented format items; I wouldn't be surprised to find that
some of my old programs still contain %r.)

What got me thinking about this was seeing a program which did

	unsigned short x;
	...
	printf("...%ld...", ..., x, ...);

and which Lint had no objection to at all.  I've used a Fortran compiler
which caught this kind of mistake at compile time, and format errors were
no less common than some of the other things Lint catches.

guy@gorodish.Sun.COM (Guy Harris) (02/08/88)

> Why not check that the arguments are consistent with the format?

The System V "lint" libraries contain items of the form:

		/*VARARGS1 PRINTFLIKE1*/
	int	printf(s) char *s; { return (0); }

Unfortunately, the System V "lint" doesn't implement "PRINTFLIKE".  However, a
Ninth Edition manual I saw did document "PRINTFLIKE", so maybe it is
implemented there; then again, "NOSTRICT" was documented in some versions of
the "lint" manual page although it wasn't implemented.
	Guy Harris
	{ihnp4, decvax, seismo, decwrl, ...}!sun!guy
	guy@sun.com

rsalz@bbn.com (Rich Salz) (02/09/88)

>  Why not check
>that the [printf, etc] arguments are consistent with the format?

Here's a program that does it as a pre-processor to lint...  If anyone
cleans it up and expands it to handle all the printf-like family, or
makes it parse the currently-mythical "/* PRINTFLIKE */" directive,
send it to me for posting...
	/r$

Subject: v06i021:  new printfck and manpage (printfck2)
Newsgroups: mod.sources
Approved: rs@mirror.UUCP
Reply-To: liam@cs.qmc.ac.uk (William Roberts)

Mod.sources: Volume 6, Issue 21
Submitted by: talcott!seismo!mcvax!cs.qmc.ac.uk!liam
Archive-name: printfck2

[ One of the major reasons why I'm sending this out is that is
  that there is now a manual page.  From now on, I will probably
  not send out anything that is missing one.  Probably exceptions
  are non-Unix source, like the recent Apollo pacman.  --r$]

The enclosed shar file is an enhanced version of printfck which
generates check code for scanf() as well as printf(), has a
manual page and comes with a makefile.  It is a straightforward
extension of the program from Guido van Rossum (mod.sources:
Volume 4, Issue 114) which was recently distributed.

William Roberts                 ARPA: liam@cs.qmc.ac.uk
Queen Mary College              UUCP: liam@qmc-cs.UUCP
LONDON, UK

--------------------CUT HERE--------------------
#! /bin/sh
#  shar:  Shell Archiver
#         Run the following with /bin/sh to create:
#             README
#             printfck.1
#             printfck.c
#             printfck.h
#             percent.c
#             Makefile
# This archive created: Fri May 30 18:24:35 BST 1986
echo shar: extracting "README" '('3109 chars')'
if test -f README
then
    echo shar: will not overwrite existing file "README"
else
cat << \SHAR_EOF > README
Mod.sources:  Volume 4, Issue 114
Submitted by: Guido van Rossum <seismo!mcvax!guido>

Here's something a colleague of mind wrote one or two years ago, and
which recently prompted some interest on the net.  Unfortunately it is
not a very finished product; I post this so that others can benefit from
it and change it to fit their needs.  I tried to compile and run it (on
a VAX running 4.2BSD) and it gave sensible output when fed with itself
as input -- I can't say more about the quality.  Foreseen use is
something like:
        printfck file.c >temp.c
        lint temp.c procent.c
Lint warnings about improper usage of any of the procent_* functions
mean you're using an incorrect argument to a % escape.  For variable
strings used as formats it doesn't help you; see also the comments at
the begin of the program.
Look in the program to find the command line options (you can feed it
your own list of printf-like functions).

I'm sorry I can't spend more time on this (well I can but don't intend
to since I have no need for it right now).  If anybody comes up with a
manual, an improved version or any other changes, I'd like to hear about
it.

Greetings,
        Guido van Rossum, CWI, Amsterdam <guido@mcvax.UUCP>

/* printfck.c - check all uses of %d, %ld, %s, %u etc. - 850325 aeb@mcvax*/
/* small fixes, made more robust, process cmdline arg - 850402 guido@boring*/
/* Copyright 1985,1986 Stichting Mathematisch Centrum. Use at own risk. */
#include        <stdio.h>

/* Feed with a list of routine names and descriptions:
 *      printf("",...)
 *      sprintf(s,"",...)
 *      fprintf(f,"",...)
 * and with a source file; produce output in which occurrences of e.g.
 *      sprintf(buf, "%s%ld", s, l)
 * are replaced by
 *      sprintf(buf, "%s%ld", procent_s(s), procent_L(l))
 * Now let lint do the checking.
 * Bugs:
 *      Cases where the format string is not explicitly given (e.g., is the
 *      result of some other routine, or looks like  bool ? "s1" : "s2")
 *      are not handled.
 *      Cases where the preprocessor produces quotes or comment delimiters
 *      or concatenates partial identifiers are not handled.
 *      We do not distinguish two sets of identifiers.
 *      Only the parts lint sees get checked - not parts between (false)
 *      #ifdef's. If the call to printf is outside #ifdef's, but some
 *      args are inside, printfck may get confused. However, this is easy
 *      to avoid:
 *
 *      THIS FAILS                      THIS WORKS
 *      ----------                      ----------
 *              printf("%s%d",                  printf("%s%d", (
 *      #ifdef debug                    #ifdef debug
 *                      "foo"                           "foo"
 *      #else                           #else
 *                      "bar"                           "bar"
 *      #endif debug                    #endif debug
 *                      , num);                         ), num);
 *
 */


30/5/86         W.T. Roberts <liam@cs.qmc.ac.uk>

I have modified printfck to handle scanf() as well as printf(),
produced a manual page and a Makefile.
SHAR_EOF
if test 3109 -ne `wc -c < README`
then
    echo shar: error transmitting "README" '('should be 3109 chars')'
else
    echo README
fi
fi
echo shar: extracting "printfck.1" '('3752 chars')'
if test -f printfck.1
then
    echo shar: will not overwrite existing file "printfck.1"
else
cat << \SHAR_EOF > printfck.1
.TH PRINTFCK 1  "18 January 1983"
.SH NAME
printfck \- modify C program to enable typechecking of printf() calls
.SH SYNOPSIS
.B printfck
[ -n ]
[ -e functionname ] ...
[ -f functionfile ] ...
[ c-program ] ...
.SH DESCRIPTION
.I Printfck
reads the C-program source from the named files (or standard
input if no arguments are given) and determines the types of
the arguments to
.IR printf (3S)
and
.IR scanf (3S)
according to the given format string. It writes to standard
output a modified version of the program, where the
.I printf()
or
.I scanf()
arguments are turned into calls to dummy routines. For example:
.PP
                printf("Name %8s not known.", x);
 becomes
                printf("Name %8s not known.", percent_s(x));
.PP
The routine percent_s() is defined within the modified program
to be a function requiring a pointer to a character, so
.IR lint (1)
can now check that variable x does indeed have the appropriate
type.  Any lint warnings about improper usage of a percent_*
function indicates that the corresponding printf argument does
not match the command string!
.PP
The program recognises the routines
.IR printf() ,
.IR sprintf() ,
.IR fprintf() ,
.IR scanf() ,
.I sscanf()
and
.IR fscanf() .
.PP
A typical way of using this would be:

                printfck part1.c part2.c part3.c  > temp.c
                lint temp.c
.SH "OPTIONS"
The options allow modification of the list of functions checked
by
.IR printfck .
This allows the correct checking of user-defined variants of
printf() or scanf()
and makes lint give more useful line numbers when it
complains.
.TP
-n
Erase the default list of functions.
.TP
-e
The following argument is the name of a function to be checked
in the same way as printf(): its first argument is a printf()
format string from which subsequent argument types may be
deduced. Note that fprintf() could not be specified in this
way, because its first argument is not the format string.
.TP
-f
The following argument is the name of a file containing a list
of (function name,
.IR n ,
.IR t )
triples, where
.I n
is the number of arguments which precede the format string
and
.I t
indicates the type of formatting involved; 0 means
printf()-style and 1 means scanf()-style.
For example, the following file is equivalent to the default
function names (# indicates a comment):
.PP
                # default specification for printfck
                printf   0        # printf( "format", arg2, ... );
                fprintf  1        # fprintf( fp, "format", arg3, ... );
                sprintf  1        # sprintf( s, "format", arg3, ... );
                scanf    0        # scanf( "format", arg2, ... );
                fscanf   1        # fscanf( fp, "format", arg3, ... );
                sscanf   1        # sscanf( s, "format", arg3, ... );
.SH "LIMITATIONS"
If the format string is not an explicit constant,
.I printfck
cannot help you. It will however do its level best, and can
cope with complicated #ifdefs, given suitable hints:

.nr x \n(.lu/2u
.in +0.5i
 THIS FAILS                \h'|\nxu' THIS WORKS
                           \h'|\nxu'
        printf("%s%d",     \h'|\nxu'         printf("%s%d", (
 #ifdef debug              \h'|\nxu' #ifdef debug
                "foo"      \h'|\nxu'                 "foo"
 #else                     \h'|\nxu' #else
                "bar"      \h'|\nxu'                 "bar"
 #endif debug              \h'|\nxu' #endif debug
                , num);    \h'|\nxu'                 ), num);
.in
.SH "BUGS"
Doesn't check arguments to see if they contain invocations of
the routines to be checked.
.SH "SEE ALSO"
printf(3S), scanf(3S), lint(1)
.SH "AUTHOR"
.nf
Mod.sources:  Volume 4, Issue 114
Submitted by: Guido van Rossum <seismo!mcvax!guido>
SHAR_EOF
if test 3752 -ne `wc -c < printfck.1`
then
    echo shar: error transmitting "printfck.1" '('should be 3752 chars')'
else
    echo printfck.1
fi
fi
echo shar: extracting "printfck.c" '('13824 chars')'
if test -f printfck.c
then
    echo shar: will not overwrite existing file "printfck.c"
else
cat << \SHAR_EOF > printfck.c
/* printfck.c - check all uses of %d, %ld, %s, %u etc. - 850325 aeb@mcvax*/
/* small fixes, made more robust, process cmdline arg - 850402 guido@boring*/
/* Copyright 1985,1986 Stichting Mathematisch Centrum. Use at own risk. */
/* $Header: printfck.c,v 1.3 86/05/30 12:31:42 liam Exp $
 * $Log:        printfck.c,v $
 * Revision 1.3  86/05/30  12:31:42  liam
 * Added facility to recognise scanf formats as well.
 *
 */

#include        <stdio.h>
#include        <strings.h>

#include "printfck.h"

/* Feed with a list of routine names and descriptions:
 *      printf("",...)
 *      sprintf(s,"",...)
 *      fprintf(f,"",...)
 * and with a source file; produce output in which occurrences of e.g.
 *      sprintf(buf, "%s%ld", s, l)
 * are replaced by
 *      sprintf(buf, "%s%ld", percent_s(s), percent_L(l))
 * Now let lint do the checking.
 * Bugs:
 *      Cases where the format string is not explicitly given (e.g., is the
 *      result of some other routine, or looks like  bool ? "s1" : "s2")
 *      are not handled.
 *      Cases where the preprocessor produces quotes or comment delimiters
 *      or concatenates partial identifiers are not handled.
 *      We do not distinguish two sets of identifiers.
 *      Only the parts lint sees get checked - not parts between (false)
 *      #ifdef's. If the call to printf is outside #ifdef's, but some
 *      args are inside, printfck may get confused. However, this is easy
 *      to avoid:
 *
 *      THIS FAILS                      THIS WORKS
 *      ----------                      ----------
 *              printf("%s%d",                  printf("%s%d", (
 *      #ifdef debug                    #ifdef debug
 *                      "foo"                           "foo"
 *      #else                           #else
 *                      "bar"                           "bar"
 *      #endif debug                    #endif debug
 *                      , num);                         ), num);
 *
 */

char *index();
char *rindex();
char *malloc();

#define MAXIRS 100

struct ir {
        char *rname;
        int pn;         /* number of args preceding format string */
        int type;       /* 0 = printf, 1 = scanf */
} irs[MAXIRS+1] = {     /* should be read in - for now explicit */
        "printf",       0,  0,
        "fprintf",      1,  0,
        "sprintf",      1,  0,
        "scanf",        0,  1,
        "fscanf",       1,  1,
        "sscanf",       1,  1,
};

int nirs;

char *progname;
char *filename = NULL;
FILE *inp;

int eof;
int peekc;
int lastc;
int linenr;

initgetcx()
{
        eof = 0;
        peekc = '\n';   /* recognize # on very first line */
        lastc = 0;      /* result of last getchar() */
        linenr = 1;
}

getcx()
{
        register int c;

        if(peekc) {
                c = peekc;
                peekc = 0;
        } else if(eof) {
                c = EOF;
        } else {
                if(lastc) {
                        putchar(lastc);
                        lastc = 0;
                }
                if((c = getc(inp)) == EOF)
                        eof++;
                else {
                        lastc = c;
                        if(c == '\n')
                                linenr++;
                }
        }

        return(c);
}

/* Note: we do not want to eliminate comments; perhaps they contain
   lint directives. */
getcy()         /* as getcx(), but skip comments */
{
        register int c = getcx();

        if(c == '/') {
                c = getcx();
                if(c == '*') {
                        while(1) {
                                c = getcx();
                                if(c == EOF)
                                        error("unfinished comment");
                                while(c == '*') {
                                        c = getcx();
                                        if(c == '/')
                                                return(getcy());
                                }
                        }
                } else {
                        peekc = c;
                        c = '/';
                }
        }
        return(c);
}

getcz()         /* as getcy(), but skip preprocessor directives */
{
        register int c = getcy();

        while(c == '\n') {
                c = getcx();
                if(c == '#') {
                        while(c != '\n') {
                                c = getcx();
                                if(c == EOF)
                                        error("incomplete line");
                                while(c == '\\') {
                                        (void) getcx(); c = getcx();
                                }
                        }
                } else {
                        peekc = c;
                        return('\n');
                }
        }
        return(c);
}

getcq()         /* as getcz() but skip strings */
{
        register int c = getcz();
        register int delim;

        if(c == '\'' || c == '"') {
                delim = c;
                while(1) {
                        c = getcx();
                        if(c == '\n' || c == EOF)
                                error("Unfinished string (delim %c)", delim);
                        if(c == '\\') {
                                (void) getcx();
                                continue;
                        }
                        if(c == delim)
                                return(getcq());
                }
        }
        return(c);
}

usage()
{
        fprintf(stderr,
          "Usage: %s [-n] [-e function] ... [-f datafile] ... [file.c] ...\n",
          progname);
        exit(2);
}

extern char *optarg;
extern int optind;

main(argc, argv)
int argc;
char **argv;
{
        register int c;
        FILE *fp;

        if (argc > 0) {
                progname = rindex(argv[0], '/');
                if (progname != NULL)
                        ++progname;
                else
                        progname = argv[0];
        }

        for (; irs[nirs].rname != NULL; ) ++nirs; /* Count defaults */

        while ((c = getopt(argc, argv, "e:nf:")) != EOF) {
                switch (c) {
                case '?':
                        usage();
                        /* NOTREACHED */
                case 'e':
                        addir(optarg, 0, 0);
                        break;
                case 'n':
                        nirs = 0;
                        irs[nirs].rname = NULL;
                        break;
                case 'f':
                        getirfile(optarg);
                        break;
                }
        }

        /* tell lint the types of the percent_* routines */

        printf("#include \"%s\"\n", PERCENT_HEADERS);

        /* now process them files.... */

        if (optind == argc)
                treat(stdin, "stdin");
        else {
                for (; optind < argc; ++optind) {
                        if (strcmp(argv[optind], "-") == 0)
                                treat(stdin, "stdin");
                        else {
                                filename = argv[optind];
                                fp = fopen(filename, "r");
                                if (fp == NULL)
                                        filerror(filename);
                                treat(fp, filename);
                                fclose(fp);
                        }
                }
        }

        /* now include the bodies of the routines */

        printf("#include \"%s\"\n", PERCENT_ROUTINES);

        exit(0);
}

treat(fp, file)
FILE *fp;
char *file;
{
        register int c;

        filename = file;
        linenr = 0;
        inp = fp;
        printf("# line 1 \"%s\"\n", file);

        initgetcx();

        while((c = getcq()) != EOF) {

                /* check for (interesting) identifiers */
                if(letter(c))
                        rd_id(c);
        }
        filename = NULL;
        linenr = 0;
        irs[nirs].rname = NULL;
}


rd_id(first)
register int first;
{
        char idf[256];
        register char *ip = idf;
        register int c;

        *ip++ = first;
        while(letdig(c = getcx()))
                if(ip-idf < sizeof(idf)-1)
                        *ip++ = c;
        peekc = c;
        *ip = 0;
        handle(idf);
}

/*VARARGS1*/
error(s, x)
char *s;
{
        printf("\n"); /* Finish incomplete output line */
        fprintf(stderr, "%s: Error (", progname);
        if (filename != NULL) fprintf(stderr, "%s, ", filename);
        fprintf(stderr, "line %d): ", linenr);
        fprintf(stderr, s, x);
        fprintf(stderr, "\n");
        exit(1);
}

/*VARARGS1*/
warning(s, x1, x2)
char *s;
{
        fprintf(stderr, "%s: Warning (", progname);
        if (filename != NULL) fprintf(stderr, "%s, ", filename);
        fprintf(stderr, "line %d): ", linenr);
        fprintf(stderr, s, x1, x2);
        fprintf(stderr, "\n");
}

filerror(file)
char *file;
{
        fprintf(stderr, "%s: can't open ", progname);
        perror(file);
        exit(2);
}

letter(c)
register int c;
{
        return(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_');
}

digit(c)
register int c;
{
        return('0' <= c && c <= '9');
}

letdig(c)
register int c;
{
        return(letter(c) || digit(c));
}

handle(idf)
register char *idf;
{
        register struct ir *irp = irs;

        while(irp->rname) {
                if(!strcmp(idf, irp->rname)) {
                        doit(irp);
                        return;
                }
                irp++;
        }
}

skipspaces()
{
        register int c;

        while(1) {
                c = getcz();
                if(c == ' ' || c == '\t' || c == '\n')
                        continue;
                peekc = c;
                return;
        }
}

doit(irp)
register struct ir *irp;
{
        register int c, cnt = irp->pn;

        skipspaces();
        if((c = getcz()) != '(') {
                peekc = c;
                warning("%s not followed by '('", irp->rname);
                return;
        }

        while(cnt--) {
                c = skiparg();
                if(c != ',') {
                        peekc = c;
                        warning("arg of %s not followed by comma", irp->rname);
                        return;
                }
        }
        skipspaces();

        /* now parse format string (if present) */
        /* (here we also avoid defining occurrences) */
        if((c = getcx()) != '"') {
                peekc = c;
                return;
        }
        if (irp->type == 0)
                do_printf(irp);
        else
                do_scanf(irp);
}

do_printf(irp)
register struct ir *irp;
{
        char fmt[256];
        register char *fp = fmt;
        register int c;

        while(1) {
                c = getcx();
                if(c == '\n' || c == EOF)
                        error("Unfinished format string");
                if(c == '"')
                        break;
                if(c != '%') {
                        if (c == '\\')
                                (void) getcx();
                        continue;
                }
                c = getcx();
                if(c == '%')
                        continue;
                if(c == '-')
                        c = getcx();
                if(c == '*') {
                        c = getcx();
                        if(fp-fmt < sizeof(fmt)-1)
                                *fp++ = '*';
                } else while(digit(c))
                        c = getcx();
                if(c == '.')
                        c = getcx();
                if(c == '*') {
                        c = getcx();
                        if(fp-fmt < sizeof(fmt)-1)
                                *fp++ = '*';
                } else while(digit(c))
                        c = getcx();
                if(c == '#')
                        c = getcx();
                if(c == 'l') {
                        c = getcx();
                        if('a' <= c && c <= 'z')
                                c -= 'a'-'A';
                        else
                                error("%%l not followed by lowercase");
                }
                if(fp-fmt < sizeof(fmt)-1)
                        *fp++ = c;
                else
                        warning("ridiculously long format");
        }
        *fp = 0;
        fp = fmt;
        skipspaces();
        while((c = getcz()) == ',') {
                if(!*fp)
                        error("%s has too many arguments", irp->rname);
                skipspaces();
                if (*fp == '*') {
                        printf("percent_star(");
                        fp++;
                } else
                        printf("percent_%c(", *fp++);
                c = skiparg();
                printf(")");
                peekc = c;
        }
        if(c != ')')
                error("%s has ill-formed argument list", irp->rname);
        if(*fp)
                error("%s has too few arguments", irp->rname);
}


do_scanf(irp)
register struct ir *irp;
{
        char fmt[256], shorten;
        register char *fp = fmt;
        register int c;
        int dummy = 0;

        while(1) {
                dummy = 0;
                c = getcx();
                if(c == '\n' || c == EOF)
                        error("Unfinished format string");
                if(c == '"')
                        break;
                if(c != '%') {
                        if (c == '\\')
                                (void) getcx();
                        continue;
                }
                c = getcx();
                if(c == '%')
                        continue;       /* %% */

                if(c == '*') {
                        dummy = 1;      /* no corresponding argument */
                        c = getcx();
                }
                while (digit(c) || c == '.' || c == '-') {
                        c = getcx();
                }
                switch(c) {
                case '[':
                        c = skipbracket();      /* scan to close bracket */
                        break;
                case 'l':
                        c = getcx();
                        if('a' <= c && c <= 'z')
                                c -= 'a'-'A';
                        else
                                error("%%l not followed by lowercase");
                        break;
                case 'h':
                        shorten = c;
                        c = getcx();
                        if('a' <= c && c <= 'z')
                                c -= 'a'-'A';
                        else
                                error("%%h not followed by lowercase");
                        break;
                default:
                        /* must be a format letter */
                        break;
                }
                if (!dummy) {
                        if(fp-fmt < sizeof(fmt)-2) {
                               if (shorten == 'h')
                                        *fp++ = 'h';
                                *fp++ = c;
                        } else
                                warning("ridiculously long format");
                }
        }
        *fp = 0;
        fp = fmt;
        skipspaces();
        while((c = getcz()) == ',') {
                if(!*fp)
                        error("%s has too many arguments", irp->rname);
                skipspaces();
                if ( (shorten = *fp) == 'h')
                        fp++;
                switch (*fp) {
                    case 's':  printf("percent_s(");   break;
                    case ']':  printf("percent_bkt("); break;
                    default:
                        if (shorten == 'h')
                                printf("percent_h%c_ptr(", *fp);
                        else
                                printf("percent_%c_ptr(", *fp);
                }
                fp++;
                c = skiparg();
                printf(")");
                peekc = c;
        }
        if(c != ')')
                error("%s has ill-formed argument list", irp->rname);
        if(*fp)
                error("%s has too few arguments", irp->rname);
}

skiparg()
{
        register int parenct = 0;
        register int c;

        parenct = 0;
        while(1) {
                c = getcq();
                if(c == EOF)
                        error("eof in arg list");
                if(!parenct && (c == ',' || c == ')'))
                        return(c);
                if(c == '(' || c == '[') {
                        parenct++;
                        continue;
                }
                if(c == ')' || c == ']') {
                        parenct--;
                        continue;
                }
        }
}

skipbracket()
{
        register int c;

        while(1) {
                c = getcx();
                if(c == EOF)
                        error("eof during %%[ ]");
                if(c == '\\') {
                        c = getcx();
                        continue;       /* avoid escaped char */
                }
                if(c == ']') return c;
        }
}

char *strsave(s)
char *s;
{
        char *t = malloc(strlen(s) + 1);
        if (t == NULL) error("out of memory");
        strcpy(t, s);
        return t;
}

getirfile(irfile)
char *irfile;
{
        FILE *fp = fopen(irfile, "r");
        char line[256];
        char name[256];
        char *end;
        int n;
        int cnt, type;

        if (fp == NULL) filerror(irfile);
        filename = irfile;
        linenr = 0;
        while (fgets(line, sizeof line, fp)) {
                ++linenr;
                end = index(line, '#');
                if (end) *end = '\0';
                n= sscanf(line, " %s %d %d %s", name, &cnt, &type, name+1);
                if (n == 0 || name[0] == '\0')
                        continue; /* Skip empty line or comment */
                if (n != 3 || cnt < 0 || (type != 0 && type != 1) )
                        error("bad format (must be %%s %%u %%u)");
                /* Should also check for valid name... */
                addir(strsave(name), cnt, type);
        }
        fclose(fp);
        filename = NULL;
        linenr = 0;
}

addir(name, cnt, type)
char *name;
int cnt, type;
{
        if (nirs >= MAXIRS) error("table overflow");
        irs[nirs].rname = name;
        irs[nirs].pn = cnt;
        irs[nirs].type = type;
        ++nirs;
}

/*
 * get option letter from argument vector
 */
int     opterr = 1,             /* useless, never set or used */
        optind = 1,             /* index into parent argv vector */
        optopt;                 /* character checked for validity */
char    *optarg;                /* argument associated with option */

#define BADCH   (int)'?'
#define EMSG    ""
#define tell(s) fputs(*nargv,stderr);fputs(s,stderr); \
                fputc(optopt,stderr);fputc('\n',stderr);return(BADCH);

getopt(nargc,nargv,ostr)
int     nargc;
char    **nargv,
        *ostr;
{
        static char     *place = EMSG;  /* option letter processing */
        register char   *oli;           /* option letter list index */
        char    *index();

        if(!*place) {                   /* update scanning pointer */
                if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place) return(EOF);
                if (*place == '-') {    /* found "--" */
                        ++optind;
                        return(EOF);
                }
        }                               /* option letter okay? */
        if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) {
                if(!*place) ++optind;
                tell(": illegal option -- ");
        }
        if (*++oli != ':') {            /* don't need argument */
                optarg = NULL;
                if (!*place) ++optind;
        }
        else {                          /* need an argument */
                if (*place) optarg = place;     /* no white space */
                else if (nargc <= ++optind) {   /* no arg */
                        place = EMSG;
                        tell(": option requires an argument -- ");
                }
                else optarg = nargv[optind];    /* white space */
                place = EMSG;
                ++optind;
        }
        return(optopt);                 /* dump back option letter */
}

SHAR_EOF
if test 13824 -ne `wc -c < printfck.c`
then
    echo shar: error transmitting "printfck.c" '('should be 13824 chars')'
else
    echo printfck.c
fi
fi
echo shar: extracting "printfck.h" '('132 chars')'
if test -f printfck.h
then
    echo shar: will not overwrite existing file "printfck.h"
else
cat << \SHAR_EOF > printfck.h
#define PERCENT_HEADERS         "/usr/src/net/printfck/percent.h"
#define PERCENT_ROUTINES        "/usr/src/net/printfck/percent.c"
SHAR_EOF
if test 132 -ne `wc -c < printfck.h`
then
    echo shar: error transmitting "printfck.h" '('should be 132 chars')'
else
    echo printfck.h
fi
fi
echo shar: extracting "percent.c" '('2094 chars')'
if test -f percent.c
then
    echo shar: will not overwrite existing file "percent.c"
else
cat << \SHAR_EOF > percent.c
/*LINTLIBRARY*/
int      percent_d(x) int      x; { return x; }
int      percent_o(x) int      x; { return x; }
int      percent_x(x) int      x; { return x; }
long     percent_D(x) long     x; { return x; }
long     percent_O(x) long     x; { return x; }
long     percent_X(x) long     x; { return x; }
double   percent_e(x) double   x; { return x; }
double   percent_f(x) double   x; { return x; }
double   percent_g(x) double   x; { return x; }
double   percent_E(x) double   x; { return x; }
double   percent_F(x) double   x; { return x; }
double   percent_G(x) double   x; { return x; }
unsigned percent_u(x) unsigned x; { return x; }
int      percent_c(x) int      x; { return x; }
char *   percent_s(x) char *   x; { return x; }

int      percent_star(x) int   x; { return x; }

int *      percent_d_ptr(x)  int *      x; { return x; }
int *      percent_o_ptr(x)  int *      x; { return x; }
int *      percent_x_ptr(x)  int *      x; { return x; }
short *    percent_hd_ptr(x) short *    x; { return x; }
short *    percent_ho_ptr(x) short *    x; { return x; }
short *    percent_hx_ptr(x) short *    x; { return x; }
long *     percent_D_ptr(x)  long *     x; { return x; }
long *     percent_O_ptr(x)  long *     x; { return x; }
long *     percent_X_ptr(x)  long *     x; { return x; }
float *    percent_e_ptr(x)  float *    x; { return x; }
float *    percent_f_ptr(x)  float *    x; { return x; }
float *    percent_g_ptr(x)  float *    x; { return x; }
double *   percent_E_ptr(x)  double *   x; { return x; }
double *   percent_F_ptr(x)  double *   x; { return x; }
double *   percent_G_ptr(x)  double *   x; { return x; }
unsigned * percent_u_ptr(x)  unsigned * x; { return x; }

int *      percent_c_ptr(x)  int *      x; { return x; }
char *     percent_bkt(x)    char *     x; { return x; }

/* NOTE: not all C compilers support unsigned long! - If your compiler rejects
 * the following lines, replace "unsigned long" with just "long"
 */
unsigned long   percent_U(x)     unsigned long   x; { return x; }
unsigned long * percent_U_ptr(x) unsigned long * x; { return x; }
SHAR_EOF
if test 2094 -ne `wc -c < percent.c`
then
    echo shar: error transmitting "percent.c" '('should be 2094 chars')'
else
    echo percent.c
fi
fi
echo shar: extracting "Makefile" '('300 chars')'
if test -f Makefile
then
    echo shar: will not overwrite existing file "Makefile"
else
cat << \SHAR_EOF > Makefile
# Makefile for printfck
#
# Change printfck.h so that PERCENT_HEADERS and PERCENT_ROUTINES
# have the correct directory paths.


all: printfck percent.h


printfck: printfck.o printfck.h
        cc -o printfck printfck.o

percent.h: percent.c
        sed -e "s/LINTLIBRARY//" -e "s/(x.*/();/" percent.c >percent.h
SHAR_EOF
if test 300 -ne `wc -c < Makefile`
then
    echo shar: error transmitting "Makefile" '('should be 300 chars')'
else
    echo Makefile
fi
fi
#         End of shar archive
exit 0
-- 
For comp.sources.unix stuff, mail to sources@uunet.uu.net.

dmk@dmk3b1.UUCP (David Keaton) (02/09/88)

In article <631@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
>	unsigned short x;
>	...
>	printf("...%ld...", ..., x, ...);
>and which Lint had no objection to at all.

     Safe C, from Catalytix in Boston, checks for this.  I don't have
their address handy at the moment, but I have used the product and it is
excellent.  (I have no connection with them except as a customer.)


-- 
					David Keaton
					dmk%dmk3b1@uunet.uu.net
					uunet!dmk3b1!dmk

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

In article <631@cresswell.quintus.UUCP>, ok@quintus.UUCP (Richard A. O'Keefe) writes:
> There's a feature missing from every Lint I've tried, which would be
> very useful, and should be easy to add (if I had sources, I'd be doing
> it right now).  Consider the stdio functions
> In each of these functions, the format argument is almost always a literal,
> and it is very easy by inspecting this literal to determine how many
> arguments should follow and what their types should be.  Why not check
> that the arguments are consistent with the format?  (This would also help
> to catch unimplemented format items; I wouldn't be surprised to find that
> some of my old programs still contain %r.)

>  From: guy@gorodish.Sun.COM (Guy Harris)
>  The System V "lint" libraries contain items of the form:
>      /*VARARGS1 PRINTFLIKE1*/
>      int    printf(s) char *s; { return (0); }
>  Unfortunately, the System V "lint" doesn't implement "PRINTFLIKE".  However, a
>  Ninth Edition manual I saw did document "PRINTFLIKE", so maybe it is
>  implemented there; then again, "NOSTRICT" was documented in some versions of
>  the "lint" manual page although it wasn't implemented.

Good timing.  Last week I added a format checker to our highly
modified BSD lint.  After I've tested it a bit more, I'll probably
post the change in a few weeks.

I couldn't figure out how to do a /*PRINTFLIKE#*/ directive with the
function definition, since that is looked at in pass2, and by then
the format string used by the calling function is long gone.

Instead, I added a directive to the function declaration.  e.g.
extern int printf(/*FORMAT1*/);    goes into <stdio.h>.

I tried linting some of the BSD source, but it didn't do much good
since Berkeley seldom bothers to include stdio.h unless the code
won't compile without it, so not many formats were actually checked.

Here's an example of what it does at the moment:

% cat xx.c
#include <stdlib.h>
#include <stdio.h>

main(argv, argc)
{
    printf("argc = %ld\n", argc, argv);
    printf("argv = %*.*s\n", 12.3, 17L, argv);
    printf("%r %d %d\n", 17);
}
% lint xx.c
xx.c:
xx.c(6): warning: format #1 "%ld" requires long, not int
xx.c(6): warning: Too many arguments for format
xx.c(7): warning: format #1 "%*.*" *-width requires int, not double
xx.c(7): warning: format #1 "%*.*s" *-precision requires int, not long
xx.c(7): warning: format #1 "%*.*s" requires string, not int
xx.c(8): warning: unknown #1 "%r" format specifier
xx.c(8): warning: Not enough arguments for format
"main", arg. 2 used inconsistently (int != pointer to pointer to char) xx.c(5)  ::  llib-lmain:crt0.c(3)
"main" result is used, but none is returned.
"printf" result is always ignored.