printfck.c - Check printf format strings
Andries Brouwer
aeb at mcvax.UUCP
Sun Mar 31 11:44:54 AEST 1985
In order to enable lint to check the correspondence between
the format specifications of printf-like functions (like %d, %ld, %s)
and the number and type of actual arguments the program given below
copies stdin to stdout, but adds a first line
#include "procent.h"
and replaces each call to a printf-like function (printf, sprintf, fprintf,
and others you may care to add to its source) such as
sprintf(buf, "A %20s%*d", s, m, n);
by
sprintf(buf, "A %20s%*d", procent_s(s), procent_d(m), procent_d(n));
Having processed all one's sources with printfck one creates files
procent.h and procent.c, where procent.h is something like
extern int procent_d();
extern long procent_D();
extern unsigned long procent_U();
extern char procent_c();
extern char *procent_s();
and procent.c is something like
int procent_d(d) int d; { return(d); }
long procent_D(D) long D; { return(D); }
unsigned long procent_U(U) unsigned long U; { return(U); }
char procent_c(c) char c; { return(c); }
char *procent_s(s) char *s; { return(s); }
Now lint can do the checking with
lint -options procent.c other_sources ...
I just wrote this as a quick hack to check the Hack & Quest sources.
If someone wants to make this into something generally useful
the first thing to do is making printfck read the list of functions
to check from a file or from its arguments.
There are some minor problems with the interaction with preprocessor
directives (e.g., parts of a printf statement between #ifdef's
or #define's containing quotes).
Perhaps this should have been done with lex.
-------------- cut here ------------------------
/* printfck.c - check all uses of %d, %ld, %s, %u etc. - 850325 aeb at mcvax*/
#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);
*
*/
struct ir {
char *rname;
int pn; /* number of args preceding format string */
} irs[] = { /* should be read in - for now explicit */
"printf", 0,
"fprintf", 1,
"sprintf", 1,
"Sprintf", 1,
"impossible", 0,
"panic", 0,
"pline", 0,
"warning", 0,
"error", 0,
(char *) 0, 0
};
char *progname;
int eof = 0;
int peekc = '\n'; /* recognize # on very first line */
int lastc = 0; /* result of last getchar() */
int 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 = getchar()) == 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 == EOF)
error("Unfinished string; delim = %c.", delim);
if(c == '\\') {
(void) getcx();
continue;
}
if(c == delim)
return(getcq());
}
}
return(c);
}
main(argc,argv)
int argc;
char **argv;
{
register int c;
progname = argc ? argv[0] : "printfck";
printf("#include \"procent.h\"\n");
while((c = getcq()) != EOF) {
/* check for (interesting) identifiers */
if(letter(c))
rd_id(c);
}
return(0);
}
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;
{
fprintf(stderr, "\n%s: Error (line %d): ", progname, linenr);
fprintf(stderr, s, x);
fprintf(stderr, "\n\n");
exit(1);
}
/*VARARGS1*/
warning(s,x1,x2)
char *s;
{
fprintf(stderr, "%s: Warning (line %d): ", progname, linenr);
fprintf(stderr, s, x1, x2);
}
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 '(' but '%c'.\n",
irp->rname, c);
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 == EOF)
error("premature end of format string");
if(c == '"')
break;
if(c != '%')
continue;
c = getcx();
if(c == '%')
continue;
if(c == '-')
c = getcx();
if(c == '*') {
c = getcx();
if(fp-fmt < sizeof(fmt)-1)
*fp++ = c;
} else while(digit(c))
c = getcx();
if(c == '.')
c = getcx();
if(c == '*') {
c = getcx();
if(fp-fmt < sizeof(fmt)-1)
*fp++ = c;
} else while(digit(c))
c = getcx();
if(c == '#')
c = getcx();
if(c == 'l') {
c = getcx();
if('a' <= c && c <= 'z')
c -= 040;
else
error("%%l not followed by lowercase");
}
if(fp-fmt < sizeof(fmt)-1)
*fp++ = c;
else
warning("ridiculously long format.\n");
}
*fp = 0;
fp = fmt;
skipspaces();
while((c = getcz()) == ',') {
if(!*fp)
error("too many arguments");
skipspaces();
printf("procent_%c(", *fp++);
c = skiparg();
printf(")");
if(c == ')' && *fp)
error("too few arguments");
if(c == ')')
return;
peekc = c;
}
}
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 == '(') {
parenct++;
continue;
}
if(c == ')') {
parenct--;
continue;
}
}
}
More information about the Comp.sources.unix
mailing list