Path: seismo!harvard!husc6!talcott!panda!sources-request From: sources-request@panda.UUCP Newsgroups: mod.sources Subject: printfck -- makes lint check (most) printf calls Message-ID: <1850@panda.UUCP> Date: 11 May 86 14:19:26 GMT Sender: jpn@panda.UUCP Lines: 694 Approved: jpn@panda.UUCP Mod.sources: Volume 4, Issue 114 Submitted by: Guido van Rossum 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 #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create the files: # printfck.c # procent.c # procent.h # This archive created: Sun May 11 10:14:38 1986 export PATH; PATH=/bin:$PATH echo shar: extracting "'printfck.c'" '(10441 characters)' if test -f 'printfck.c' then echo shar: will not over-write 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. */ #include /* 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); * */ char *index(); char *rindex(); char *malloc(); #define MAXIRS 100 struct ir { char *rname; int pn; /* number of args preceding format string */ } irs[MAXIRS+1] = { /* should be read in - for now explicit */ "printf", 0, "fprintf", 1, "sprintf", 1, }; int nirs; char *progname = "printfck"; 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); break; case 'n': nirs = 0; irs[nirs].rname = NULL; break; case 'f': getirfile(optarg); break; } } printf("#include \"procent.h\"\n"); if (optind == argc) treat(stdin, NULL); else { for (; optind < argc; ++optind) { if (strcmp(argv[optind], "-") == 0) treat(stdin, NULL); else { filename = argv[optind]; fp = fopen(filename, "r"); if (fp == NULL) filerror(filename); treat(fp, filename); fclose(fp); } } } exit(0); } treat(fp, file) FILE *fp; char *file; { register int c; filename = file; linenr = 0; inp = fp; 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; } domore(irp); } domore(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++ = 'd'; } else while(digit(c)) c = getcx(); if(c == '.') c = getcx(); if(c == '*') { c = getcx(); if(fp-fmt < sizeof(fmt)-1) *fp++ = 'd'; } 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(); printf("procent_%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); } 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; } } } 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; 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 %s", name, &cnt, name+1); if (n == 0 || name[0] == '\0') continue; /* Skip empty line or comment */ if (n != 2 || cnt < 0) error("bad format (must be %%s %%u)"); /* Should also check for valid name... */ addir(strsave(name), cnt); } fclose(fp); filename = NULL; linenr = 0; } addir(name, cnt) char *name; int cnt; { if (nirs >= MAXIRS) error("table overflow"); irs[nirs].rname = name; irs[nirs].pn = cnt; ++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 10441 -ne "`wc -c < 'printfck.c'`" then echo shar: error transmitting "'printfck.c'" '(should have been 10441 characters)' fi fi echo shar: extracting "'procent.c'" '(848 characters)' if test -f 'procent.c' then echo shar: will not over-write existing file "'procent.c'" else cat << \SHAR_EOF > 'procent.c' /*LINTLIBRARY*/ int procent_d(x) int x; { return x; } int procent_o(x) int x; { return x; } int procent_x(x) int x; { return x; } long procent_D(x) long x; { return x; } long procent_O(x) long x; { return x; } long procent_X(x) long x; { return x; } double procent_e(x) double x; { return x; } double procent_f(x) double x; { return x; } double procent_g(x) double x; { return x; } double procent_E(x) double x; { return x; } double procent_F(x) double x; { return x; } double procent_G(x) double x; { return x; } /* NOTE: not all C compilers support unsigned long! - If your compiler rejects * the following line, replace "unsigned long" with just "long" */ unsigned long procent_U(x) unsigned long x; { return x; } unsigned procent_u(x) unsigned x; { return x; } int procent_c(x) int x; { return x; } char *procent_s(x) char *x; { return x; } SHAR_EOF if test 848 -ne "`wc -c < 'procent.c'`" then echo shar: error transmitting "'procent.c'" '(should have been 848 characters)' fi fi echo shar: extracting "'procent.h'" '(542 characters)' if test -f 'procent.h' then echo shar: will not over-write existing file "'procent.h'" else cat << \SHAR_EOF > 'procent.h' /* procent.h * this file is the header for files being lint'ed using printfck */ int procent_d(); int procent_o(); int procent_x(); long procent_D(); long procent_O(); long procent_X(); double procent_e(); double procent_f(); double procent_g(); double procent_E(); double procent_F(); double procent_G(); /* NOTE: not all C compilers support unsigned long! - If your compiler rejects * the following line, replace "unsigned long" with just "long" */ unsigned long procent_U(); unsigned procent_u(); int procent_c(); char *procent_s(); SHAR_EOF if test 542 -ne "`wc -c < 'procent.h'`" then echo shar: error transmitting "'procent.h'" '(should have been 542 characters)' fi fi exit 0 # End of shell archive