PATCH: Add format string suggestions to -Wformat warnings

Dan Hipschman dsh@google.com
Tue Aug 14 23:51:00 GMT 2007


Hi.

This patch adds suggestions for format specifiers, based on type, to warnings
with the -Wformat flag.  For example:

$ cat foo.c
#include <stdio.h>
int main (void)
{
  printf ("%d", 0.0);
  printf ("%lu", 0u);
  printf ("%lu", sizeof 0);
  return 0;
}

$ gcc-svn -std=c99 -Wformat foo.c
foo.c: In function 'main':
foo.c:4: warning: format '%d' expects type 'int', but argument 2 has type 'double'
foo.c:4: warning: acceptable formats for this type are: a, A, e, E, f, F, g, G
foo.c:5: warning: format '%lu' expects type 'long unsigned int', but argument 2 has type 'unsigned int'
foo.c:5: warning: acceptable formats for this type are: o, u, x, X
foo.c:6: warning: format '%lu' expects type 'long unsigned int', but argument 2 has type 'unsigned int'
foo.c:6: warning: acceptable formats for this type are: zo, zu, zx, zX

It is sensitive to the standard:

$ gcc-svn -std=c89 -Wformat foo.c
foo.c: In function 'main':
foo.c:4: warning: format '%d' expects type 'int', but argument 2 has type 'double'
foo.c:4: warning: acceptable formats for this type are: e, E, f, g, G
foo.c:5: warning: format '%lu' expects type 'long unsigned int', but argument 2 has type 'unsigned int'
foo.c:5: warning: acceptable formats for this type are: o, u, x, X
foo.c:6: warning: format '%lu' expects type 'long unsigned int', but argument 2 has type 'unsigned int'
foo.c:6: warning: acceptable formats for this type are: o, u, x, X

See the included testcases for more complete examples.  It works with %n, and
checks the type for constness.  This only adds suggestions for printf, but it
shouldn't be hard to add suggestions for scanf and the others.  It works with
sizeof, pointer differences and wide string literals, but to do this I had to
make separate type nodes for size_type_node, ptrdiff_type_node and
wchar_type_node, since currently these type nodes are indistinguishable from
the primitive types they alias.  (For example, "sizeof x" is compiled into a
constant with the same type as size_type_node, which may be unsigned int, but
after that it's impossible to tell that that constant came from a sizeof.
TYPE_IS_SIZETYPE doesn't work: see the bottom of c_sizeof_or_alignof_type.)
Simply copying the type nodes seemed like the least-possibly harmful way to do
this, and I've bootstrapped and run "make -k check" on i686-linux-gnu with no
new problems.  I should note that there is one failure in the testcases
included with this patch, which is basically this:

#include <stdio.h>
#include <wchar.h>
int main (void)
{
  wint_t w = L'x';
  printf ("%c", w);
  return 0;
}

which produces no warnings, when I think is should.  This is a problem with
the code in svn, though, since suggestions are only made if a warning is issued
because the types aren't acceptable.  I can try to fix that in a separate
patch.

Dan


gcc/
2007-08-14  Dan Hipschman  <dsh@google.com>

	* c-format.h (format_suggestions): New structure.  New FMT_SUG_* flags.
	* c-format.c (format_wanted_type): Add suggestions field.
	(printf_suggestions): New global.
	(format_types_orig): Add suggestions field initializers.
	(format_type_warning): Add suggestions and arg parameters.  Remove
	arg_type parameter.  Use format suggestions in warnings.
	(check_format_info_main): Use suggestions.
	(check_format_types): Pass orig_cur_param instead of orig_cur_type to
	format_type_warning.
	* c-common.c (copy_type_with_name): New function.
	(c_common_nodes_and_builtins): Use it.

testsuite/
2007-08-14  Dan Hipschman  <dsh@google.com>

	* gcc.dg/format/ext-suggest-1.c: New test.
	* gcc.dg/format/ext-suggest-2.c: New test.
	* gcc.dg/format/dfp-suggest-1.c: New test.
	* gcc.dg/format/typedef-suggest-1.c: New test.
	* gcc.dg/format/xopen-1.c: Add an excess-errors check for suggestions.
	* gcc.dg/format/branch-1.c: Likewise.
	* gcc.dg/format/diag-1.c: Likewise.
	* gcc.dg/format/multattr-3.c: Likewise.
	* gcc.dg/format/dfp-printf-1.c: Likewise.
	* gcc.dg/format/unnamed-1.c: Likewise.

Index: gcc/testsuite/gcc.dg/format/ext-suggest-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/ext-suggest-1.c	(revision 0)
+++ gcc/testsuite/gcc.dg/format/ext-suggest-1.c	(revision 0)
@@ -0,0 +1,63 @@
+/* Copyright (C) 2007 Free Software Foundation.  */
+/* Contributed by Dan Hipschman <dsh@google.com>.  */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu99 -Wformat" } */
+
+#include "format.h"
+
+void
+foo (signed char hhd, unsigned char c, short hd, unsigned short hu, size_t zu,
+     ssize_t zd, ptrdiff_t td, intmax_t jd, uintmax_t ju, wchar_t *ls,
+     wint_t lc, void *p, float f, long double Lf, const char *s,
+     const void *p2)
+{
+  char a[2] = " ";
+  char *td1 = &a[1], *td2 = &a[0];
+  int n;
+  long ln;
+  long long lln;
+  printf ("%p", hhd); /* { dg-warning ": hhd, hhi" } */
+  printf ("%p", c); /* { dg-warning ": c, hho, hhu, hhx, hhX" } */
+  printf ("%p", a[0]); /* { dg-warning ": d, i" } */
+  printf ("%p", hd); /* { dg-warning ": hd, hi" } */
+  printf ("%p", hu); /* { dg-warning ": ho, hu, hx, hX" } */
+  printf ("%p", 0); /* { dg-warning ": d, i" } */
+  printf ("%p", 0u); /* { dg-warning ": o, u, x, X" } */
+  printf ("%p", 0L); /* { dg-warning ": ld, li" } */
+  printf ("%p", 0uL); /* { dg-warning ": lo, lu, lx, lX" } */
+  printf ("%p", 0LL); /* { dg-warning ": lld, lli" } */
+  printf ("%d", 0L); /* { dg-warning ": ld, li" } */
+  printf ("%u", 0uL); /* { dg-warning ": lo, lu, lx, lX" } */
+  printf ("%p", 0uLL); /* { dg-warning ": llo, llu, llx, llX" } */
+  printf ("%p", zu); /* { dg-warning ": zo, zu, zx, zX" } */
+  printf ("%p", sizeof 0); /* { dg-warning ": zo, zu, zx, zX" } */
+  printf ("%p", zd); /* { dg-warning ": zd, zi" } */
+  printf ("%p", td); /* { dg-warning ": td, ti" } */
+  printf ("%p", td1 - td2); /* { dg-warning ": td, ti" } */
+  printf ("%p", jd); /* { dg-warning ": jd, ji" } */
+  printf ("%p", ju); /* { dg-warning ": jo, ju, jx, jX" } */
+  printf ("%d", a); /* { dg-warning ": s" } */
+  printf ("%c", "foo"); /* { dg-warning ": s" } */
+  printf ("%d", s); /* { dg-warning ": s" } */
+  printf ("%c", ls); /* { dg-warning ": ls" } */
+  printf ("%s", L"foo"); /* { dg-warning ": ls" } */
+  printf ("%c", lc); /* { dg-warning ": lc" } */
+  printf ("%c", L'x'); /* { dg-warning ": lc" } */
+  printf ("%d", p); /* { dg-warning ": p" } */
+  printf ("%x", p2); /* { dg-warning ": p" } */
+  printf ("%d", f); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  printf ("%d", 0.0); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  printf ("%f", Lf); /* { dg-warning ": La, LA, Le, LE, Lf, LF, Lg, LG" } */
+  printf ("%f", &hhd); /* { dg-warning ": hhn" } */
+  printf ("%f", (const signed char *) &hhd);
+  printf ("%f", &hd); /* { dg-warning ": hn" } */
+  printf ("%f", (const short *) &hd);
+  printf ("%f", &n); /* { dg-warning ": n" } */
+  printf ("%f", (const int *) &n);
+  printf ("%f", &ln); /* { dg-warning ": ln" } */
+  printf ("%f", (const long *) &ln);
+  printf ("%f", &lln); /* { dg-warning ": lln" } */
+  printf ("%f", (const long long *) &lln);
+}
+
+/* { dg-excess-errors "expects .* but argument .* has" } */
Index: gcc/testsuite/gcc.dg/format/typedef-suggest-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/typedef-suggest-1.c	(revision 0)
+++ gcc/testsuite/gcc.dg/format/typedef-suggest-1.c	(revision 0)
@@ -0,0 +1,44 @@
+/* Copyright (C) 2007 Free Software Foundation.  */
+/* Contributed by Dan Hipschman <dsh@google.com>.  */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu99 -Wformat" } */
+
+extern void foo (const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
+
+/* See if we can fool the compiler.  */
+typedef double size_t;
+typedef double ptrdiff_t;
+typedef double wchar_t;
+typedef double wint_t;
+typedef double ssize_t;
+typedef char *intmax_t;
+typedef void *uintmax_t;
+
+char a[2];
+
+void
+bar (size_t s1, ptrdiff_t p1, wchar_t c1, wchar_t *pc1, wint_t i1, ssize_t ss1,
+     intmax_t m1, uintmax_t m2)
+{
+  size_t s2 = 0.0;
+  foo ("%p", sizeof 0); /* { dg-warning ": zo, zu, zx, zX" } */
+  foo ("%p", s1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%p", s2); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%zu", s2); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%p", &a[0] - &a[1]); /* { dg-warning ": td, ti" } */
+  foo ("%p", p1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%ti", p1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%s", L'x'); /* { dg-warning ": lc" } */
+  foo ("%s", c1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%lc", c1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%s", i1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%lc", i1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%s", L"x"); /* { dg-warning ": ls" } */
+  foo ("%s", pc1);
+  foo ("%s", ss1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%zd", ss1); /* { dg-warning ": a, A, e, E, f, F, g, G" } */
+  foo ("%i", m1); /* { dg-warning ": s" } */
+  foo ("%ju", m2); /* { dg-warning ": p" } */
+}
+
+/* { dg-excess-errors "expects .* but argument .* has" } */
Index: gcc/testsuite/gcc.dg/format/xopen-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/xopen-1.c	(revision 127489)
+++ gcc/testsuite/gcc.dg/format/xopen-1.c	(working copy)
@@ -123,3 +123,5 @@ foo (int i, unsigned int u, wint_t lc, w
   printf ("%1$d%1$d", i);
   scanf ("%1$d%1$d", ip); /* { dg-warning "more than once" "multiple use of scanf argument" } */
 }
+
+/* { dg-excess-errors "acceptable formats for .* are" } */
Index: gcc/testsuite/gcc.dg/format/branch-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/branch-1.c	(revision 127489)
+++ gcc/testsuite/gcc.dg/format/branch-1.c	(working copy)
@@ -25,3 +25,5 @@ foo (long l, int nfoo)
   printf (NULL, "foo"); /* { dg-warning "too many" "NULL extra args" } */
   /* { dg-warning "null" "null format arg" { target *-*-* } 25 } */
 }
+
+/* { dg-excess-errors "acceptable formats for .* are" } */
Index: gcc/testsuite/gcc.dg/format/diag-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/diag-1.c	(revision 127489)
+++ gcc/testsuite/gcc.dg/format/diag-1.c	(working copy)
@@ -16,3 +16,5 @@ foo (double d)
      'unsigned int' or similar.  */
   printf ("%zu", d); /* { dg-warning "size_t" "size_t format warning" } */
 }
+
+/* { dg-excess-errors "acceptable formats for .* are" } */
Index: gcc/testsuite/gcc.dg/format/multattr-3.c
===================================================================
--- gcc/testsuite/gcc.dg/format/multattr-3.c	(revision 127489)
+++ gcc/testsuite/gcc.dg/format/multattr-3.c	(working copy)
@@ -26,3 +26,5 @@ foo (long l, int nfoo)
   printf (ngettext ("%d foo", (nfoo > 0) ? "%ld foos" : "no foos", nfoo), nfoo); /* { dg-warning "long int" "wrong type" } */
   printf (ngettext ("%d foo", (nfoo > 0) ? "%d foos" : "%ld foos", nfoo), nfoo); /* { dg-warning "long int" "wrong type" } */
 }
+
+/* { dg-excess-errors "acceptable formats for .* are" } */
Index: gcc/testsuite/gcc.dg/format/ext-suggest-2.c
===================================================================
--- gcc/testsuite/gcc.dg/format/ext-suggest-2.c	(revision 0)
+++ gcc/testsuite/gcc.dg/format/ext-suggest-2.c	(revision 0)
@@ -0,0 +1,60 @@
+/* Copyright (C) 2007 Free Software Foundation.  */
+/* Contributed by Dan Hipschman <dsh@google.com>.  */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu89 -Wformat" } */
+
+#include "format.h"
+
+void
+foo (signed char hhd, unsigned char c, short hd, unsigned short hu, size_t zu,
+     ssize_t zd, ptrdiff_t td, wchar_t *ls, wint_t lc, void *p, float f,
+     long double Lf, const char *s, const void *p2)
+{
+  char a[2] = " ";
+  char *td1 = &a[1], *td2 = &a[0];
+  int n;
+  long ln;
+  long long lln;
+  printf ("%p", hhd); /* { dg-warning ": d, i" } */
+  printf ("%p", c); /* { dg-warning ": c" } */
+  printf ("%p", a[0]); /* { dg-warning ": d, i" } */
+  printf ("%p", hd); /* { dg-warning ": hd, hi" } */
+  printf ("%p", hu); /* { dg-warning ": ho, hu, hx, hX" } */
+  printf ("%p", 0); /* { dg-warning ": d, i" } */
+  printf ("%p", 0u); /* { dg-warning ": o, u, x, X" } */
+  printf ("%p", 0L); /* { dg-warning ": ld, li" } */
+  printf ("%p", 0uL); /* { dg-warning ": lo, lu, lx, lX" } */
+  printf ("%p", 0LL);
+  printf ("%d", 0L); /* { dg-warning ": ld, li" } */
+  printf ("%u", 0uL); /* { dg-warning ": lo, lu, lx, lX" } */
+  printf ("%p", 0uLL);
+  printf ("%p", zu); /* { dg-warning ": l?o, l?u, l?x, l?X" } */
+  printf ("%p", sizeof 0); /* { dg-warning ": l?o, l?u, l?x, l?X" } */
+  printf ("%p", zd); /* { dg-warning ": l?d, l?i" } */
+  printf ("%p", td); /* { dg-warning ": l?d, l?i" } */
+  printf ("%p", td1 - td2); /* { dg-warning ": l?d, l?i" } */
+  printf ("%d", a); /* { dg-warning ": s" } */
+  printf ("%c", "foo"); /* { dg-warning ": s" } */
+  printf ("%d", s); /* { dg-warning ": s" } */
+  printf ("%c", ls);
+  printf ("%s", L"foo");
+  printf ("%c", lc); /* { dg-warning ": l?d, l?i" } */
+  printf ("%c", L'x'); /* { dg-warning ": l?d, l?i" } */
+  printf ("%d", p); /* { dg-warning ": p" } */
+  printf ("%x", p2); /* { dg-warning ": p" } */
+  printf ("%d", f); /* { dg-warning ": e, E, f, g, G" } */
+  printf ("%d", 0.0); /* { dg-warning ": e, E, f, g, G" } */
+  printf ("%f", Lf);
+  printf ("%f", &hhd);
+  printf ("%f", (const signed char *) &hhd);
+  printf ("%f", &hd); /* { dg-warning ": hn" } */
+  printf ("%f", (const short *) &hd);
+  printf ("%f", &n); /* { dg-warning ": n" } */
+  printf ("%f", (const int *) &n);
+  printf ("%f", &ln); /* { dg-warning ": ln" } */
+  printf ("%f", (const long *) &ln);
+  printf ("%f", &lln);
+  printf ("%f", (const long long *) &lln);
+}
+
+/* { dg-excess-errors "expects .* but argument .* has" } */
Index: gcc/testsuite/gcc.dg/format/dfp-printf-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/dfp-printf-1.c	(revision 127489)
+++ gcc/testsuite/gcc.dg/format/dfp-printf-1.c	(working copy)
@@ -120,3 +120,5 @@ foo (_Decimal32 x, _Decimal64 y, _Decima
   printf ("%0-#DDg\n", z); /* { dg-warning "flag ignored" "ignore flag" } */
   printf ("% DDG\n", z);
 }
+
+/* { dg-excess-errors "acceptable formats for .* are" } */
Index: gcc/testsuite/gcc.dg/format/unnamed-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/unnamed-1.c	(revision 127489)
+++ gcc/testsuite/gcc.dg/format/unnamed-1.c	(working copy)
@@ -22,3 +22,5 @@ f (TItype x)
   printf("%d", 141592653589793238462643383279502884197169399375105820974944); /* { dg-warning "expects type" } */
   /* { dg-warning "unsigned only|too large" "constant" { target *-*-* } 22 } */
 }
+
+/* { dg-excess-errors "acceptable formats for .* are" } */
Index: gcc/testsuite/gcc.dg/format/dfp-suggest-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/dfp-suggest-1.c	(revision 0)
+++ gcc/testsuite/gcc.dg/format/dfp-suggest-1.c	(revision 0)
@@ -0,0 +1,17 @@
+/* Copyright (C) 2007 Free Software Foundation.  */
+/* Contributed by Dan Hipschman <dsh@google.com>.  */
+/* { dg-do compile } */
+/* { dg-require-effective-target dfp } */
+/* { dg-options "-std=gnu89 -Wformat" } */
+
+#include "format.h"
+
+void
+foo (_Decimal32 Hf, _Decimal64 Df, _Decimal128 DDf)
+{
+  printf ("%f", Hf); /* { dg-warning ": He, HE, Hf, HF, Hg, HG" } */
+  printf ("%f", Df); /* { dg-warning ": De, DE, Df, DF, Dg, DG" } */
+  printf ("%f", DDf); /* { dg-warning ": DDe, DDE, DDf, DDF, DDg, DDG" } */
+}
+
+/* { dg-excess-errors "expects .* but argument .* has" } */
Index: gcc/c-format.c
===================================================================
--- gcc/c-format.c	(revision 127489)
+++ gcc/c-format.c	(working copy)
@@ -267,6 +267,8 @@ typedef struct format_wanted_type
   /* Whether the argument, dereferenced once, is read from and so
      must not be a NULL pointer.  */
   int reading_from_flag;
+  /* Format conversion suggestions, if available, otherwise NULL.  */
+  const format_suggestions *suggestions;
   /* If warnings should be of the form "field precision should have
      type 'int'", the name to use (in this case "field precision"),
      otherwise NULL, for "format expects type 'long'" type
@@ -498,6 +500,43 @@ static const format_flag_pair strfmon_fl
   { 0, 0, 0, 0 }
 };
 
+static const format_suggestions printf_suggestions[] =
+{
+  { &wchar_type_node, "wchar_t", FMT_SUG_POINTER, STD_C94, "ls" },
+  { &wchar_type_node, "wchar_t", 0, STD_C94, "lc" },
+  { &wint_type_node, "wint_t", 0, STD_C94, "lc" },
+  { &signed_size_type_node, "ssize_t", 0, STD_C99, "zd, zi" },
+  { &size_type_node, "size_t", 0, STD_C99, "zo, zu, zx, zX" },
+  { &ptrdiff_type_node, "ptrdiff_t", 0, STD_C99, "td, ti" },
+  { &intmax_type_node, "intmax_t", 0, STD_C99, "jd, ji" },
+  { &uintmax_type_node, "uintmax_t", 0, STD_C99, "jo, ju, jx, jX" },
+  { &dfloat32_type_node, "_Decimal32", 0, STD_EXT, "He, HE, Hf, HF, Hg, HG" },
+  { &dfloat64_type_node, "_Decimal64", 0, STD_EXT, "De, DE, Df, DF, Dg, DG" },
+  { &dfloat128_type_node, "_Decimal128", 0, STD_EXT, "DDe, DDE, DDf, DDF, DDg, DDG" },
+  { &unsigned_char_type_node, NULL, 0, STD_C99, "c, hho, hhu, hhx, hhX" },
+  { &unsigned_char_type_node, NULL, 0, STD_C89, "c" },
+  { &signed_char_type_node, NULL, 0, STD_C99, "hhd, hhi" },
+  { &short_unsigned_type_node, NULL, 0, STD_C89, "ho, hu, hx, hX" },
+  { &short_integer_type_node, NULL, 0, STD_C89, "hd, hi" },
+  { &unsigned_type_node, NULL, 0, STD_C89, "o, u, x, X" },
+  { &integer_type_node, NULL, 0, STD_C89, "d, i" },
+  { &long_integer_type_node, NULL, 0, STD_C89, "ld, li" },
+  { &long_long_integer_type_node, NULL, 0, STD_C9L, "lld, lli" },
+  { &char_type_node, NULL, FMT_SUG_POINTER, STD_C89, "s" },
+  { &long_unsigned_type_node, NULL, 0, STD_C89, "lo, lu, lx, lX" },
+  { &long_long_unsigned_type_node, NULL, 0, STD_C9L, "llo, llu, llx, llX" },
+  { &double_type_node, NULL, 0, STD_C99, "a, A, e, E, f, F, g, G" },
+  { &long_double_type_node, NULL, 0, STD_C99, "La, LA, Le, LE, Lf, LF, Lg, LG" },
+  { &double_type_node, NULL, 0, STD_C89, "e, E, f, g, G" },
+  { &long_double_type_node, NULL, 0, STD_C89, "Le, LE, Lf, Lg, LG" },
+  { &signed_char_type_node, NULL, FMT_SUG_POINTER|FMT_SUG_WRITE, STD_C99, "hhn" },
+  { &short_integer_type_node, NULL, FMT_SUG_POINTER|FMT_SUG_WRITE, STD_C89, "hn" },
+  { &integer_type_node, NULL, FMT_SUG_POINTER|FMT_SUG_WRITE, STD_C89, "n" },
+  { &long_integer_type_node, NULL, FMT_SUG_POINTER|FMT_SUG_WRITE, STD_C89, "ln" },
+  { &long_long_integer_type_node, NULL, FMT_SUG_POINTER|FMT_SUG_WRITE, STD_C9L, "lln" },
+  { &void_type_node, NULL, FMT_SUG_POINTER, STD_C89, "p" },
+  { NULL, NULL, 0, 0, NULL }
+};
 
 static const format_char_info print_char_table[] =
 {
@@ -713,60 +752,60 @@ static const format_char_info monetary_c
 static const format_kind_info format_types_orig[] =
 {
   { "printf",   printf_length_specs,  print_char_table, " +#0-'I", NULL,
-    printf_flag_specs, printf_flag_pairs,
+    printf_flag_specs, printf_flag_pairs, printf_suggestions,
     FMT_FLAG_ARG_CONVERT|FMT_FLAG_DOLLAR_MULTIPLE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_EMPTY_PREC_OK,
     'w', 0, 'p', 0, 'L',
     &integer_type_node, &integer_type_node
   },
   { "asm_fprintf",   asm_fprintf_length_specs,  asm_fprintf_char_table, " +#0-", NULL,
-    asm_fprintf_flag_specs, asm_fprintf_flag_pairs,
+    asm_fprintf_flag_specs, asm_fprintf_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT|FMT_FLAG_EMPTY_PREC_OK,
     'w', 0, 'p', 0, 'L',
     NULL, NULL
   },
   { "gcc_diag",   gcc_diag_length_specs,  gcc_diag_char_table, "q+", NULL,
-    gcc_diag_flag_specs, gcc_diag_flag_pairs,
+    gcc_diag_flag_specs, gcc_diag_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT,
     0, 0, 'p', 0, 'L',
     NULL, &integer_type_node
   },
   { "gcc_tdiag",   gcc_tdiag_length_specs,  gcc_tdiag_char_table, "q+", NULL,
-    gcc_tdiag_flag_specs, gcc_tdiag_flag_pairs,
+    gcc_tdiag_flag_specs, gcc_tdiag_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT,
     0, 0, 'p', 0, 'L',
     NULL, &integer_type_node
   },
   { "gcc_cdiag",   gcc_cdiag_length_specs,  gcc_cdiag_char_table, "q+", NULL,
-    gcc_cdiag_flag_specs, gcc_cdiag_flag_pairs,
+    gcc_cdiag_flag_specs, gcc_cdiag_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT,
     0, 0, 'p', 0, 'L',
     NULL, &integer_type_node
   },
   { "gcc_cxxdiag",   gcc_cxxdiag_length_specs,  gcc_cxxdiag_char_table, "q+#", NULL,
-    gcc_cxxdiag_flag_specs, gcc_cxxdiag_flag_pairs,
+    gcc_cxxdiag_flag_specs, gcc_cxxdiag_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT,
     0, 0, 'p', 0, 'L',
     NULL, &integer_type_node
   },
   { "gcc_gfc", gcc_gfc_length_specs, gcc_gfc_char_table, "", NULL,
-    NULL, gcc_gfc_flag_pairs,
+    NULL, gcc_gfc_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT,
     0, 0, 0, 0, 0,
     NULL, NULL
   },
   { "scanf",    scanf_length_specs,   scan_char_table,  "*'I", NULL,
-    scanf_flag_specs, scanf_flag_pairs,
+    scanf_flag_specs, scanf_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT|FMT_FLAG_SCANF_A_KLUDGE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_ZERO_WIDTH_BAD|FMT_FLAG_DOLLAR_GAP_POINTER_OK,
     'w', 0, 0, '*', 'L',
     NULL, NULL
   },
   { "strftime", NULL,                 time_char_table,  "_-0^#", "EO",
-    strftime_flag_specs, strftime_flag_pairs,
+    strftime_flag_specs, strftime_flag_pairs, NULL,
     FMT_FLAG_FANCY_PERCENT_OK, 'w', 0, 0, 0, 0,
     NULL, NULL
   },
   { "strfmon",  strfmon_length_specs, monetary_char_table, "=^+(!-", NULL,
-    strfmon_flag_specs, strfmon_flag_pairs,
+    strfmon_flag_specs, strfmon_flag_pairs, NULL,
     FMT_FLAG_ARG_CONVERT, 'w', '#', 'p', 0, 'L',
     NULL, NULL
   }
@@ -834,7 +873,8 @@ static const format_flag_spec *get_flag_
 
 static void check_format_types (format_wanted_type *, const char *, int);
 static void format_type_warning (const char *, const char *, int, tree,
-				 int, const char *, tree, int);
+				 int, const char *, tree, int,
+				 const format_suggestions *);
 
 /* Decode a format type from a string, returning the type, or
    format_type_error if not valid, in which case the caller should print an
@@ -1616,6 +1656,7 @@ check_format_info_main (format_check_res
 		  width_wanted_type.name = _("field width");
 		  width_wanted_type.param = cur_param;
 		  width_wanted_type.arg_num = arg_num;
+		  width_wanted_type.suggestions = NULL;
 		  width_wanted_type.next = NULL;
 		  if (last_wanted_type != 0)
 		    last_wanted_type->next = &width_wanted_type;
@@ -1718,6 +1759,7 @@ check_format_info_main (format_check_res
 		  precision_wanted_type.name = _("field precision");
 		  precision_wanted_type.param = cur_param;
 		  precision_wanted_type.arg_num = arg_num;
+		  precision_wanted_type.suggestions = NULL;
 		  precision_wanted_type.next = NULL;
 		  if (last_wanted_type != 0)
 		    last_wanted_type->next = &precision_wanted_type;
@@ -2077,6 +2119,7 @@ check_format_info_main (format_check_res
 	      wanted_type_ptr->name = NULL;
 	      wanted_type_ptr->param = cur_param;
 	      wanted_type_ptr->arg_num = arg_num;
+	      wanted_type_ptr->suggestions = fki->suggestions;
 	      wanted_type_ptr->next = NULL;
 	      if (last_wanted_type != 0)
 		last_wanted_type->next = wanted_type_ptr;
@@ -2123,7 +2166,7 @@ check_format_types (format_wanted_type *
     {
       tree cur_param;
       tree cur_type;
-      tree orig_cur_type;
+      tree orig_cur_param;
       tree wanted_type;
       int arg_num;
       int i;
@@ -2132,7 +2175,7 @@ check_format_types (format_wanted_type *
       cur_type = TREE_TYPE (cur_param);
       if (cur_type == error_mark_node)
 	continue;
-      orig_cur_type = cur_type;
+      orig_cur_param = cur_param;
       char_type_flag = 0;
       wanted_type = types->wanted_type;
       arg_num = types->arg_num;
@@ -2211,8 +2254,8 @@ check_format_types (format_wanted_type *
 	    {
 	      format_type_warning (types->name, format_start, format_length,
 				   wanted_type, types->pointer_count,
-				   types->wanted_type_name, orig_cur_type,
-				   arg_num);
+				   types->wanted_type_name, orig_cur_param,
+				   arg_num, types->suggestions);
 	      break;
 	    }
 	}
@@ -2260,7 +2303,8 @@ check_format_types (format_wanted_type *
       /* Now we have a type mismatch.  */
       format_type_warning (types->name, format_start, format_length,
 			   wanted_type, types->pointer_count,
-			   types->wanted_type_name, orig_cur_type, arg_num);
+			   types->wanted_type_name, orig_cur_param, arg_num,
+			   types->suggestions);
     }
 }
 
@@ -2272,15 +2316,20 @@ check_format_types (format_wanted_type *
    FORMAT_LENGTH is its length.  WANTED_TYPE is the type the argument
    should have after POINTER_COUNT pointer dereferences.
    WANTED_NAME_NAME is a possibly more friendly name of WANTED_TYPE,
-   or NULL if the ordinary name of the type should be used.  ARG_TYPE
-   is the type of the actual argument.  ARG_NUM is the number of that
-   argument.  */
+   or NULL if the ordinary name of the type should be used.  ARG
+   is the actual argument.  ARG_NUM is the number of that
+   argument.  SUGGESTIONS is used to suggest format conversions that
+   are acceptable for the argument type provided.  It may be NULL.  */
 static void
 format_type_warning (const char *descr, const char *format_start,
 		     int format_length, tree wanted_type, int pointer_count,
-		     const char *wanted_type_name, tree arg_type, int arg_num)
+		     const char *wanted_type_name, tree arg, int arg_num,
+		     const format_suggestions *suggestions)
 {
+  const char *suggest = NULL;
+  tree arg_type;
   char *p;
+  arg_type = TREE_TYPE (arg);
   /* If ARG_TYPE is a typedef with a misleading name (for example,
      size_t but not the standard size_t expected by printf %zu), avoid
      printing the typedef name.  */
@@ -2310,6 +2359,71 @@ format_type_warning (const char *descr, 
       memset (p + 1, '*', pointer_count);
       p[pointer_count + 1] = 0;
     }
+
+  if (suggestions)
+    {
+      tree type = TREE_TYPE (arg);
+      for ( ; suggestions->type; ++suggestions)
+	{
+	  tree sug_type;
+	  tree test_type;
+	  if (C_STD_VER < ADJ_STD (suggestions->std)
+	      && (suggestions->std != STD_EXT || flag_iso))
+	    continue;
+	  if (suggestions->flags & FMT_SUG_POINTER)
+	    {
+	      if (TREE_CODE (type) == POINTER_TYPE)
+		test_type = TREE_TYPE (type);
+	      else
+		continue;
+	    }
+	  else
+	    test_type = type;
+	  if (suggestions->flags & FMT_SUG_WRITE)
+	    {
+	      gcc_assert (suggestions->flags & FMT_SUG_POINTER);
+	      if (TYPE_READONLY (test_type))
+		continue;
+	    }
+	  sug_type = *suggestions->type;
+	  if (suggestions->name)
+	    {
+	      if ((test_type == size_type_node
+		   || test_type == ptrdiff_type_node
+		   || test_type == wchar_type_node)
+		  && test_type == sug_type)
+		break;
+	      else
+		{
+		  const char *type_name;
+		  if (TYPE_NAME (test_type)
+		      && TREE_CODE (TYPE_NAME (test_type)) == TYPE_DECL)
+		    type_name = get_name (TYPE_NAME (test_type));
+		  else
+		    type_name = NULL;
+		  if (!type_name || strcmp (type_name, suggestions->name) != 0)
+		    continue;
+		}
+	    }
+	  test_type = TYPE_MAIN_VARIANT (test_type);
+	  if (lang_hooks.types_compatible_p (test_type, sug_type))
+	    break;
+	  if (!(suggestions->flags & FMT_SUG_POINTER))
+	    {
+	      tree pro_type = lang_hooks.types.type_promotes_to (sug_type);
+	      if (lang_hooks.types_compatible_p (pro_type, test_type)
+		  && (TREE_CODE (arg) == NOP_EXPR
+		      || TREE_CODE (arg) == CONVERT_EXPR))
+		{
+		  tree orig_arg_type = TREE_TYPE (TREE_OPERAND (arg, 0));
+		  if (lang_hooks.types_compatible_p (sug_type, orig_arg_type))
+		    break;
+		}
+	    }
+	}
+      suggest = suggestions->desc;
+    }
+
   if (wanted_type_name)
     {
       if (descr)
@@ -2333,6 +2447,9 @@ format_type_warning (const char *descr, 
 		 "but argument %d has type %qT",
 		 format_length, format_start, wanted_type, p, arg_num, arg_type);
     }
+  if (suggest)
+    warning (OPT_Wformat, "acceptable formats for this type are: %s",
+	     suggest);
 }
 
 
Index: gcc/c-format.h
===================================================================
--- gcc/c-format.h	(revision 127489)
+++ gcc/c-format.h	(working copy)
@@ -200,6 +200,33 @@ typedef struct
 } format_flag_pair;
 
 
+/* Flags used to specify types in format string suggestions.  */
+enum
+{
+  /* Set if the required type is a pointer to the type specified in the
+     format_suggestions structure.  */
+  FMT_SUG_POINTER = 1,
+  /* Set if the required type must be writable.  The FMT_SUG_POINTER flag
+     must also be set.  */
+  FMT_SUG_WRITE = 2
+};
+
+/* Structure mapping a type to format conversion suggestions.  */
+typedef struct
+{
+  /* The type wanted by the conversions.  */
+  tree *type;
+  /* A name for this type if it's typedef'd, otherwise NULL.  */
+  const char *name;
+  /* Flags specified by the FMT_SUG_ constants.  */
+  unsigned flags;
+  /* The standard to which the conversions belong.  */
+  enum format_std_version std;
+  /* The conversion suggestions for use with this type.  */
+  const char *desc;
+} format_suggestions;
+
+
 /* Structure describing a particular kind of format processed by GCC.  */
 typedef struct
 {
@@ -218,6 +245,8 @@ typedef struct
   const format_flag_spec *flag_specs;
   /* Details of bad combinations of flags.  */
   const format_flag_pair *bad_flag_pairs;
+  /* Suggestions for conversions based on type.  */
+  const format_suggestions *suggestions;
   /* Flags applicable to this kind of format.  */
   int flags;
   /* Flag character to treat a width as, or 0 if width not used.  */
Index: gcc/c-common.c
===================================================================
--- gcc/c-common.c	(revision 127489)
+++ gcc/c-common.c	(working copy)
@@ -3480,6 +3480,19 @@ c_define_builtins (tree va_list_ref_type
     mudflap_init ();
 }
 
+
+/* Look up the type with identifier NAME and return a copy of it.  We want
+   some of the standard, but non-primitive, types, such as size_t, to be
+   copies of the types they represent, so we can still tell them apart.  */
+
+static tree
+copy_type_with_name (const char *name)
+{
+  return
+    copy_node (TREE_TYPE (identifier_global_value (get_identifier (name))));
+}
+
+
 /* Build tree nodes and builtin functions common to both C and C++ language
    frontends.  */
 
@@ -3569,8 +3582,7 @@ c_common_nodes_and_builtins (void)
   /* `unsigned long' is the standard type for sizeof.
      Note that stddef.h uses `unsigned long',
      and this must agree, even if long and int are the same size.  */
-  size_type_node =
-    TREE_TYPE (identifier_global_value (get_identifier (SIZE_TYPE)));
+  size_type_node = copy_type_with_name (SIZE_TYPE);
   signed_size_type_node = c_common_signed_type (size_type_node);
   set_sizetype (size_type_node);
 
@@ -3653,8 +3665,7 @@ c_common_nodes_and_builtins (void)
 			  (char_type_node, TYPE_QUAL_CONST));
 
   /* This is special for C++ so functions can be overloaded.  */
-  wchar_type_node = get_identifier (MODIFIED_WCHAR_TYPE);
-  wchar_type_node = TREE_TYPE (identifier_global_value (wchar_type_node));
+  wchar_type_node = copy_type_with_name (MODIFIED_WCHAR_TYPE);
   wchar_type_size = TYPE_PRECISION (wchar_type_node);
   if (c_dialect_cxx ())
     {
@@ -3683,8 +3694,7 @@ c_common_nodes_and_builtins (void)
     TREE_TYPE (identifier_global_value (get_identifier (UINTMAX_TYPE)));
 
   default_function_type = build_function_type (integer_type_node, NULL_TREE);
-  ptrdiff_type_node
-    = TREE_TYPE (identifier_global_value (get_identifier (PTRDIFF_TYPE)));
+  ptrdiff_type_node = copy_type_with_name (PTRDIFF_TYPE);
   unsigned_ptrdiff_type_node = c_common_unsigned_type (ptrdiff_type_node);
 
   lang_hooks.decls.pushdecl



More information about the Gcc-patches mailing list