This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Format checking reorganisation patch


Here is an initial format checking reorganisation patch.  It moves
details of length modifiers and the standard versions in which length
modifiers, conversion specifiers and their combinations were
introduced, into tables and out of ad hoc conditionals in the code.
This allows some diagnostics to be improved: GCC will no longer refer
to its invented `H' length modifier when giving messages about the
user's `hh', nor to `q' when the user used `ll'; and messages about
size_t and similar formats used with the wrong type argument will now
give a meaningful type name.  A test case is added for these improved
diagnostics.  Yet another global tree node is used to provide the C
language size_t type, replacing the TYPE_DOMAIN kludge formerly used
to make %z formats work.

With my previous patch to update testcase warning regexps applied,
bootstraps with no regressions on i686-pc-linux-gnu.  OK to commit?

gcc/ChangeLog:
2000-08-26  Joseph S. Myers  <jsm28@cam.ac.uk>

	* c-common.h (enum c_tree_index): Add CTI_C_SIZE_TYPE.
	(c_size_type_node): Define.
	* c-decl.c (init_decl_processing): Initialize c_size_type_node.
	* c-common.c (enum format_lengths, enum format_std_version,
	format_length_info, format_type_detail, BADLEN, NOLENGTHS,
	format_kind_info, printf_length_specs, scanf_length_specs, T89_I,
	T99_I, T89_L, T99_LL, TEX_LL, T89_S, T89_UI, T99_UI, T89_UL,
	T99_ULL, TEX_ULL, T89_US, T89_F, T99_F, T89_D, T99_D, T89_LD,
	T99_LD, T89_C, T99_SC, T99_UC, T89_V, T94_W, TEX_W, T94_WI,
	TEX_WI, T99_ST, T99_SST, T99_PD, T99_UPD, T99_IM, T99_UIM,
	format_types): Define.
	(format_char_info, print_char_table, scan_char_table,
	time_char_table): Rearrange for new organization of information
	about format length modifiers and standard versions.
	(T_ST): Redefine to use c_size_type_node.
	(check_format_info): Obtain information about length modifiers and
	standard versions from tables.  Adjust warning message wordings.
	Use the name from the user's program for `ll' and `hh' length
	modifiers in warning messages.  Use more informative names for
	wanted types where available (for wchar_t, wint_t, size_t, signed
	size_t, ptrdiff_t, unsigned ptrdiff_t, intmax_t and uintmax_t).

gcc/testsuite/ChangeLog:
2000-08-26  Joseph S. Myers  <jsm28@cam.ac.uk>

	* gcc.dg/format-diag-1.c: New test.

--- c-common.h.orig	Fri Aug 25 01:26:26 2000
+++ c-common.h	Fri Aug 25 17:50:04 2000
@@ -91,6 +91,7 @@
     CTI_SIGNED_WCHAR_TYPE,
     CTI_UNSIGNED_WCHAR_TYPE,
     CTI_WINT_TYPE,
+    CTI_C_SIZE_TYPE, /* For format checking only.  */
     CTI_SIGNED_SIZE_TYPE, /* For format checking only.  */
     CTI_UNSIGNED_PTRDIFF_TYPE, /* For format checking only.  */
     CTI_WIDEST_INT_LIT_TYPE,
@@ -125,6 +126,7 @@
 #define signed_wchar_type_node		c_global_trees[CTI_SIGNED_WCHAR_TYPE]
 #define unsigned_wchar_type_node	c_global_trees[CTI_UNSIGNED_WCHAR_TYPE]
 #define wint_type_node			c_global_trees[CTI_WINT_TYPE]
+#define c_size_type_node		c_global_trees[CTI_C_SIZE_TYPE]
 #define signed_size_type_node		c_global_trees[CTI_SIGNED_SIZE_TYPE]
 #define unsigned_ptrdiff_type_node	c_global_trees[CTI_UNSIGNED_PTRDIFF_TYPE]
 #define widest_integer_literal_type_node c_global_trees[CTI_WIDEST_INT_LIT_TYPE]
--- c-decl.c.orig	Thu Aug 24 20:30:14 2000
+++ c-decl.c	Thu Aug 24 23:19:15 2000
@@ -3019,6 +3019,7 @@ init_decl_processing ()
   if (flag_traditional && TREE_UNSIGNED (t))
     t = signed_type (t);
 
+  c_size_type_node = t;
   set_sizetype (t);
 
   /* Create the widest literal types.  */
--- c-common.c.orig	Fri Aug 25 15:23:57 2000
+++ c-common.c	Fri Aug 25 21:45:50 2000
@@ -1184,128 +1184,257 @@
 /* Check a printf/fprintf/sprintf/scanf/fscanf/sscanf format against
    a parameter list.  */
 
+/* The meaningfully distinct length modifiers for format checking recognised
+   by GCC.  */
+enum format_lengths {
+  FMT_LEN_none,
+  FMT_LEN_hh,
+  FMT_LEN_h,
+  FMT_LEN_l,
+  FMT_LEN_ll,
+  FMT_LEN_L,
+  FMT_LEN_z,
+  FMT_LEN_t,
+  FMT_LEN_j,
+  FMT_LEN_MAX
+};
+
+
+/* The standard versions in which various format features appeared.  */
+enum format_std_version {
+  STD_C89,
+  STD_C94,
+  STD_C99,
+  STD_EXT
+};
+
+
+/* Structure describing a length modifier supported in format checking, and
+   possibly a doubled version such as "hh".  */
+typedef struct {
+  /* Name of the single-character length modifier.  */
+  const char *name;
+  /* Index into a format_char_info.types array.  */
+  enum format_lengths index;
+  /* Standard version this length appears in.  */
+  enum format_std_version std;
+  /* Same, if the modifier can be repeated, or NULL if it can't.  */
+  const char *double_name;
+  enum format_lengths double_index;
+  enum format_std_version double_std;
+} format_length_info;
+
+
+/* Structure desribing the combination of a conversion specifier
+   (or a set of specifiers which act identically) and a length modifier.  */
+typedef struct {
+  /* The standard version this combination of length and type appeared in.
+     This is only relevant if greater than those for length and type
+     individually; otherwise it is ignored.  */
+  enum format_std_version std;
+  /* The name to use for the type, if different from that generated internally
+     (e.g., "signed size_t").  */
+  const char *name;
+  /* The type itself.  */
+  tree *type;
+} format_type_detail;
+
+
+/* Macros to fill out tables of these.  */
+#define BADLEN	{ 0, NULL, NULL }
+#define NOLENGTHS	{ BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
+
+
+/* Structure desribing a format conversion specifier (or a set of specifiers
+   which act identically), and the length modifiers used with it.  */
+typedef struct {
+  const char *format_chars;
+  int pointer_count;
+  enum format_std_version std;
+  /* Types accepted for each length modifier.  */
+  format_type_detail types[FMT_LEN_MAX];
+  /* List of other modifier characters allowed with these options.
+     This lists flags, and additionally "w" for width, "p" for precision,
+     "c" for generic character pointers being allowed, "a" for scanf
+     "a" allocation extension (not applicable in C99 mode), "*" for
+     scanf suppression, "2" for strftime two digit year formats, "3"
+     for strftime formats giving two digit years in some locales, "E"
+     and "O" for those strftime modifiers, and "o" if use of strftime "O"
+     is a GNU extension beyond C99.  */
+  const char *flag_chars;
+} format_char_info;
+
+
+/* Structure describing a particular kind of format processed by GCC.  */
+typedef struct
+{
+  /* The name of this kind of format, for use in diagnostics.  */
+  const char *name;
+  /* Specifications of the length modifiers accepted; possibly NULL.  */
+  const format_length_info *length_char_specs;
+  /* Details of the conversion specification characters accepted.  */
+  const format_char_info *conversion_specs;
+} format_kind_info;
+
+
+static const format_length_info printf_length_specs[] = {
+  { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99 },
+  { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C99 },
+  { "q", FMT_LEN_ll, STD_EXT, NULL, 0, 0 },
+  { "L", FMT_LEN_L, STD_C89, NULL, 0, 0 },
+  { "z", FMT_LEN_z, STD_C99, NULL, 0, 0 },
+  { "Z", FMT_LEN_z, STD_EXT, NULL, 0, 0 },
+  { "t", FMT_LEN_t, STD_C99, NULL, 0, 0 },
+  { "j", FMT_LEN_j, STD_C99, NULL, 0, 0 },
+  { NULL, 0, 0, NULL, 0, 0 }
+};
+
+
+/* This differs from printf_length_specs only in that "Z" is not accepted.  */
+static const format_length_info scanf_length_specs[] = {
+  { "h", FMT_LEN_h, STD_C89, "hh", FMT_LEN_hh, STD_C99 },
+  { "l", FMT_LEN_l, STD_C89, "ll", FMT_LEN_ll, STD_C99 },
+  { "q", FMT_LEN_ll, STD_EXT, NULL, 0, 0 },
+  { "L", FMT_LEN_L, STD_C89, NULL, 0, 0 },
+  { "z", FMT_LEN_z, STD_C99, NULL, 0, 0 },
+  { "t", FMT_LEN_t, STD_C99, NULL, 0, 0 },
+  { "j", FMT_LEN_j, STD_C99, NULL, 0, 0 },
+  { NULL, 0, 0, NULL, 0, 0 }
+};
+
+
 #define T_I	&integer_type_node
+#define T89_I	{ STD_C89, NULL, T_I }
+#define T99_I	{ STD_C99, NULL, T_I }
 #define T_L	&long_integer_type_node
+#define T89_L	{ STD_C89, NULL, T_L }
 #define T_LL	&long_long_integer_type_node
+#define T99_LL	{ STD_C99, NULL, T_LL }
+#define TEX_LL	{ STD_EXT, NULL, T_LL }
 #define T_S	&short_integer_type_node
+#define T89_S	{ STD_C89, NULL, T_S }
 #define T_UI	&unsigned_type_node
+#define T89_UI	{ STD_C89, NULL, T_UI }
+#define T99_UI	{ STD_C99, NULL, T_UI }
 #define T_UL	&long_unsigned_type_node
+#define T89_UL	{ STD_C89, NULL, T_UL }
 #define T_ULL	&long_long_unsigned_type_node
+#define T99_ULL	{ STD_C99, NULL, T_ULL }
+#define TEX_ULL	{ STD_EXT, NULL, T_ULL }
 #define T_US	&short_unsigned_type_node
+#define T89_US	{ STD_C89, NULL, T_US }
 #define T_F	&float_type_node
+#define T89_F	{ STD_C89, NULL, T_F }
+#define T99_F	{ STD_C99, NULL, T_F }
 #define T_D	&double_type_node
+#define T89_D	{ STD_C89, NULL, T_D }
+#define T99_D	{ STD_C99, NULL, T_D }
 #define T_LD	&long_double_type_node
+#define T89_LD	{ STD_C89, NULL, T_LD }
+#define T99_LD	{ STD_C99, NULL, T_LD }
 #define T_C	&char_type_node
+#define T89_C	{ STD_C89, NULL, T_C }
 #define T_SC	&signed_char_type_node
+#define T99_SC	{ STD_C99, NULL, T_SC }
 #define T_UC	&unsigned_char_type_node
+#define T99_UC	{ STD_C99, NULL, T_UC }
 #define T_V	&void_type_node
+#define T89_V	{ STD_C89, NULL, T_V }
 #define T_W	&wchar_type_node
+#define T94_W	{ STD_C94, "wchar_t", T_W }
+#define TEX_W	{ STD_EXT, "wchar_t", T_W }
 #define T_WI	&wint_type_node
-#define T_ST    &sizetype
+#define T94_WI	{ STD_C94, "wint_t", T_WI }
+#define TEX_WI	{ STD_EXT, "wint_t", T_WI }
+#define T_ST    &c_size_type_node
+#define T99_ST	{ STD_C99, "size_t", T_ST }
 #define T_SST   &signed_size_type_node
+#define T99_SST	{ STD_C99, "signed size_t", T_SST }
 #define T_PD    &ptrdiff_type_node
+#define T99_PD	{ STD_C99, "ptrdiff_t", T_PD }
 #define T_UPD   &unsigned_ptrdiff_type_node
+#define T99_UPD	{ STD_C99, "unsigned ptrdiff_t", T_UPD }
 #define T_IM    NULL /* intmax_t not yet implemented.  */
+#define T99_IM	{ STD_C99, "intmax_t", T_IM }
 #define T_UIM   NULL /* uintmax_t not yet implemented.  */
+#define T99_UIM	{ STD_C99, "uintmax_t", T_UIM }
 
-typedef struct {
-  const char *format_chars;
-  int pointer_count;
-  /* Type of argument if no length modifier is used.  */
-  tree *nolen;
-  /* Type of argument if length modifier for shortening to byte is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *hhlen;
-  /* Type of argument if length modifier for shortening is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *hlen;
-  /* Type of argument if length modifier `l' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *llen;
-  /* Type of argument if length modifier `q' or `ll' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *qlen;
-  /* Type of argument if length modifier `L' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *bigllen;
-  /* Type of argument if length modifiers 'z' or `Z' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *zlen;
-  /* Type of argument if length modifier 't' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *tlen;
-  /* Type of argument if length modifier 'j' is used.
-     If NULL, then this modifier is not allowed.  */
-  tree *jlen;
-  /* List of other modifier characters allowed with these options.  */
-  const char *flag_chars;
-} format_char_info;
+static const format_char_info print_char_table[] = {
+  /* C89 conversion specifiers.  */
+  { "di",  0, STD_C89, { T89_I,   T99_I,   T89_I,   T89_L,   T99_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM  }, "-wp0 +'I" },
+  { "oxX", 0, STD_C89, { T89_UI,  T99_UI,  T89_UI,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "-wp0#"    },
+  { "u",   0, STD_C89, { T89_UI,  T99_UI,  T89_UI,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "-wp0'I"   },
+  { "fgG", 0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#'" },
+  { "eE",  0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#"  },
+  { "c",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  T94_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-w"       },
+  { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wpc"     },
+  { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wc"      },
+  { "n",   1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  BADLEN,  T99_SST, T99_PD,  T99_IM  }, ""         },
+  /* C99 conversion specifiers.  */
+  { "F",   0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#'" },
+  { "aA",  0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +#"  },
+  /* X/Open conversion specifiers.  */
+  { "C",   0, STD_EXT, { TEX_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-w"       },
+  { "S",   1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wp"      },
+  /* GNU conversion specifiers.  */
+  { "m",   0, STD_EXT, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "-wp"      },
+  { NULL,  0, 0, NOLENGTHS, NULL }
+};
 
-static format_char_info print_char_table[] = {
-  { "di",	0,	T_I,	T_I,	T_I,	T_L,	T_LL,	T_LL,	T_SST,	T_PD,	T_IM,	"-wp0 +'I"	},
-  { "oxX",	0,	T_UI,	T_UI,	T_UI,	T_UL,	T_ULL,	T_ULL,	T_ST,	T_UPD,	T_UIM,	"-wp0#"		},
-  { "u",	0,	T_UI,	T_UI,	T_UI,	T_UL,	T_ULL,	T_ULL,	T_ST,	T_UPD,	T_UIM,	"-wp0'I"		},
-/* A GNU extension.  */
-  { "m",	0,	T_V,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"-wp"		},
-  { "fFgG",	0,	T_D,	NULL,	NULL,	T_D,	NULL,	T_LD,	NULL,	NULL,	NULL,	"-wp0 +#'"	},
-  { "eEaA",	0,	T_D,	NULL,	NULL,	T_D,	NULL,	T_LD,	NULL,	NULL,	NULL,	"-wp0 +#"	},
-  { "c",	0,	T_I,	NULL,	NULL,	T_WI,	NULL,	NULL,	NULL,	NULL,	NULL,	"-w"		},
-  { "C",	0,	T_WI,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"-w"		},
-  { "s",	1,	T_C,	NULL,	NULL,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	"-wpc"		},
-  { "S",	1,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"-wp"		},
-  { "p",	1,	T_V,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"-wc"		},
-  { "n",	1,	T_I,	T_SC,	T_S,	T_L,	T_LL,	NULL,	T_SST,	T_PD,	T_IM,	""		},
-  { NULL,	0,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL		}
+static const format_char_info scan_char_table[] = {
+  /* C89 conversion specifiers.  */
+  { "di",    1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM  }, "*w"   },
+  { "ouxX",  1, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T99_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM }, "*w"   },
+  { "efgEG", 1, STD_C89, { T89_F,   BADLEN,  BADLEN,  T89_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN  }, "*w"   },
+  { "c",     1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*cw"  },
+  { "s",     1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*acw" },
+  { "[",     1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*acw" },
+  { "p",     2, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*w"   },
+  { "n",     1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T99_LL,  BADLEN,  T99_SST, T99_PD,  T99_IM  }, ""     },
+  /* C99 conversion specifiers.  */
+  { "FaA",   1, STD_C99, { T99_F,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN  }, "*w"   },
+  /* X/Open conversion specifiers.  */
+  { "C",     1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*w"   },
+  { "S",     1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "*aw"  },
+  { NULL, 0, 0, NOLENGTHS, NULL }
 };
 
-static format_char_info scan_char_table[] = {
-  { "di",	1,	T_I,	T_SC,	T_S,	T_L,	T_LL,	T_LL,	T_SST,	T_PD,	T_IM,	"*w"	},
-  { "ouxX",	1,	T_UI,	T_UC,	T_US,	T_UL,	T_ULL,	T_ULL,	T_ST,	T_UPD,	T_UIM,	"*w"	},
-  { "efFgEGaA",	1,	T_F,	NULL,	NULL,	T_D,	NULL,	T_LD,	NULL,	NULL,	NULL,	"*w"	},
-  { "c",	1,	T_C,	NULL,	NULL,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	"*cw"	},
-  { "s",	1,	T_C,	NULL,	NULL,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	"*acw"	},
-  { "[",	1,	T_C,	NULL,	NULL,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	"*acw"	},
-  { "C",	1,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"*w"	},
-  { "S",	1,	T_W,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"*aw"	},
-  { "p",	2,	T_V,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	"*w"	},
-  { "n",	1,	T_I,	T_SC,	T_S,	T_L,	T_LL,	NULL,	T_SST,	T_PD,	T_IM,	""	},
-  { NULL,	0,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL,	NULL	}
+static format_char_info time_char_table[] = {
+  /* C89 conversion specifiers.  */
+  { "ABZa",		0, STD_C89, NOLENGTHS, "^#" },
+  { "b",		0, STD_C89, NOLENGTHS, "^" },
+  { "cx", 		0, STD_C89, NOLENGTHS, "3E" },
+  { "HIMSUWdmw",	0, STD_C89, NOLENGTHS, "-_0Ow" },
+  { "j",		0, STD_C89, NOLENGTHS, "-_0Oow" },
+  { "p",		0, STD_C89, NOLENGTHS, "#" },
+  { "X",		0, STD_C89, NOLENGTHS, "E" },
+  { "y", 		0, STD_C89, NOLENGTHS, "2EO-_0w" },
+  { "Y",		0, STD_C89, NOLENGTHS, "-_0EOow" },
+  { "%",		0, STD_C89, NOLENGTHS, "" },
+  /* C99 conversion specifiers.  */
+  { "C",		0, STD_C99, NOLENGTHS, "-_0EOow" },
+  { "D", 		0, STD_C99, NOLENGTHS, "2" },
+  { "eVu",		0, STD_C99, NOLENGTHS, "-_0Ow" },
+  { "FRTnrt",		0, STD_C99, NOLENGTHS, "" },
+  { "g", 		0, STD_C99, NOLENGTHS, "2Oo-_0w" },
+  { "G",		0, STD_C99, NOLENGTHS, "-_0Oow" },
+  { "h",		0, STD_C99, NOLENGTHS, "^" },
+  { "z",		0, STD_C99, NOLENGTHS, "Oo" },
+  /* GNU conversion specifiers.  */
+  { "kls",		0, STD_EXT, NOLENGTHS, "-_0Ow" },
+  { "P",		0, STD_EXT, NOLENGTHS, "" },
+  { NULL,		0, 0, NOLENGTHS, NULL }
 };
 
-/* Handle format characters recognized by glibc's strftime.c.
-   '2' - MUST do years as only two digits
-   '3' - MAY do years as only two digits (depending on locale)
-   'E' - E modifier is acceptable
-   'O' - O modifier is acceptable to Standard C
-   'o' - O modifier is acceptable as a GNU extension
-   '9' - added to the C standard in C99
-   'G' - other GNU extensions  */
 
-static format_char_info time_char_table[] = {
-  { "y", 		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "2EO-_0w" },
-  { "D", 		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "29" },
-  { "g", 		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "2Oo-_0w9" },
-  { "cx", 		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "3E" },
-  { "%",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "" },
-  { "X",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "E" },
-  { "FRTnrt",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "9" },
-  { "P",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "G" },
-  { "HIMSUWdmw",	0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Ow" },
-  { "e",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Ow9" },
-  { "j",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Oow" },
-  { "Vu",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Ow9" },
-  { "G",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0Oow9" },
-  { "z",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "Oo9" },
-  { "kls",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0OGw" },
-  { "ABZa",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "^#" },
-  { "p",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "#" },
-  { "b",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "^" },
-  { "h",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "^9" },
-  { "Y",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0EOow" },
-  { "C",		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "-_0EOow9" },
-  { NULL,		0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+/* This must be in the same order as enum format_type.  */
+static const format_kind_info format_types[] = {
+  { "printf",   printf_length_specs, print_char_table },
+  { "scanf",    scanf_length_specs,  scan_char_table  },
+  { "strftime", NULL,                time_char_table  }
 };
 
+
 typedef struct function_format_info
 {
   struct function_format_info *next;  /* next structure on the list */
@@ -1687,16 +1816,22 @@
   int i;
   int arg_num;
   int suppressed, wide, precise;
-  int length_char = 0;
+  const char *length_chars = NULL;
+  enum format_lengths length_chars_val = FMT_LEN_none;
+  enum format_std_version length_chars_std = STD_C89;
   int format_char;
   int format_length;
   tree format_tree;
   tree cur_param;
   tree cur_type;
   tree wanted_type;
+  enum format_std_version wanted_type_std;
+  const char *wanted_type_name;
   tree first_fillin_param;
   const char *format_chars;
-  format_char_info *fci = NULL;
+  const format_kind_info *fki = NULL;
+  const format_length_info *fli = NULL;
+  const format_char_info *fci = NULL;
   char flag_chars[8];
   /* -1 if no conversions taking an operand have been found; 0 if one has
      and it didn't use $; 1 if $ formats are in use.  */
@@ -1801,6 +1936,7 @@
 
   first_fillin_param = params;
   init_dollar_format_checking (info->first_arg_num, first_fillin_param);
+  fki = &format_types[info->format_type];
   while (1)
     {
       int aflag;
@@ -2049,52 +2185,44 @@
 
       aflag = 0;
 
-      if (info->format_type != strftime_format_type)
+      fli = fki->length_char_specs;
+      if (fli)
 	{
-	  if (*format_chars == 'h' || *format_chars == 'l')
-	    length_char = *format_chars++;
-	  else if (*format_chars == 'q' || *format_chars == 'L')
-	    {
-	      length_char = *format_chars++;
-	      if (length_char == 'q' && pedantic)
-		warning ("ISO C does not support the `%c' length modifier",
-			 length_char);
-	    }
-	  else if (*format_chars == 'z'
-		   || (*format_chars == 'Z'
-		       && info->format_type == printf_format_type))
+	  while (fli->name != 0 && fli->name[0] != *format_chars)
+	    fli++;
+	  if (fli->name != 0)
 	    {
-	      length_char = *format_chars++;
-	      if (pedantic)
+	      format_chars++;
+	      if (fli->double_name != 0 && fli->name[0] == *format_chars)
 		{
-		  if (length_char == 'Z')
-		    warning ("ISO C does not support the `%c' length modifier",
-			     length_char);
-		  else if (!flag_isoc99)
-		    warning ("ISO C89 does not support the `%c' length modifier",
-			     length_char);
+		  format_chars++;
+		  length_chars = fli->double_name;
+		  length_chars_val = fli->double_index;
+		  length_chars_std = fli->double_std;
+		}
+	      else
+		{
+		  length_chars = fli->name;
+		  length_chars_val = fli->index;
+		  length_chars_std = fli->std;
 		}
-	    }
-	  else if (*format_chars == 't' || *format_chars == 'j')
-	    {
-	      length_char = *format_chars++;
-	      if (pedantic && !flag_isoc99)
-		warning ("ISO C89 does not support the `%c' length modifier",
-			 length_char);
 	    }
 	  else
-	    length_char = 0;
-	  if (length_char == 'l' && *format_chars == 'l')
-	    {
-	      length_char = 'q', format_chars++;
-	      if (pedantic && !flag_isoc99)
-		warning ("ISO C89 does not support the `ll' length modifier");
-	    }
-	  else if (length_char == 'h' && *format_chars == 'h')
 	    {
-	      length_char = 'H', format_chars++;
-	      if (pedantic && !flag_isoc99)
-		warning ("ISO C89 does not support the `hh' length modifier");
+	      length_chars = NULL;
+	      length_chars_val = FMT_LEN_none;
+	      length_chars_std = STD_C89;
+	    }
+	  if (pedantic)
+	    {
+	      /* Warn if the length modifier is non-standard.  */
+	      if (length_chars_std == STD_EXT)
+		warning ("ISO C does not support the `%s' %s length modifier",
+			 length_chars, fki->name);
+	      else if ((length_chars_std == STD_C99 && !flag_isoc99)
+		       || (length_chars_std == STD_C94 && !flag_isoc94))
+		warning ("ISO C89 does not support the `%s' %s length modifier",
+			 length_chars, fki->name);
 	    }
 	  if (*format_chars == 'a' && info->format_type == scanf_format_type
 	      && !flag_isoc99)
@@ -2107,8 +2235,8 @@
 		  format_chars++;
 		}
 	    }
-	  if (suppressed && length_char != 0)
-	    warning ("use of `*' and `%c' together in format", length_char);
+	  if (suppressed && length_chars_val != FMT_LEN_none)
+	    warning ("use of `*' and `%s' together in format", length_chars);
 	}
       format_char = *format_chars;
       if (format_char == 0
@@ -2117,30 +2245,8 @@
 	  warning ("conversion lacks type at end of format");
 	  continue;
 	}
-      /* The m, C, and S formats are GNU extensions.  */
-      if (pedantic && info->format_type != strftime_format_type
-	  && (format_char == 'm' || format_char == 'C' || format_char == 'S'))
-	warning ("ISO C does not support the `%c' format", format_char);
-      /* The a, A and F formats are C99 extensions.  */
-      if (pedantic && info->format_type != strftime_format_type
-	  && (format_char == 'a' || format_char == 'A' || format_char == 'F')
-	  && !flag_isoc99)
-	warning ("ISO C89 does not support the `%c' format", format_char);
       format_chars++;
-      switch (info->format_type)
-	{
-	case printf_format_type:
-	  fci = print_char_table;
-	  break;
-	case scanf_format_type:
-	  fci = scan_char_table;
-	  break;
-	case strftime_format_type:
-	  fci = time_char_table;
-	  break;
-	default:
-	  abort ();
-	}
+      fci = fki->conversion_specs;
       while (fci->format_chars != 0
 	     && index (fci->format_chars, format_char) == 0)
 	  ++fci;
@@ -2156,10 +2262,13 @@
 	}
       if (pedantic)
 	{
-	  if (index (fci->flag_chars, 'G') != 0)
-	    warning ("ISO C does not support `%%%c'", format_char);
-	  if (index (fci->flag_chars, '9') != 0 && !flag_isoc99)
-	    warning ("ISO C89 does not support `%%%c'", format_char);
+	  if (fci->std == STD_EXT)
+	    warning ("ISO C does not support the `%%%c' %s format",
+		     format_char, fki->name);
+	  else if ((fci->std == STD_C99 && !flag_isoc99)
+		   || (fci->std == STD_C94 && !flag_isoc94))
+	    warning ("ISO C89 does not support the `%%%c' %s format",
+		     format_char, fki->name);
 	  if (index (flag_chars, 'O') != 0)
 	    {
 	      if (index (fci->flag_chars, 'o') != 0)
@@ -2224,45 +2333,28 @@
 	      || format_char == 'x' || format_char == 'X'))
 	warning ("`0' flag ignored with precision specifier and `%c' format",
 		 format_char);
-      switch (length_char)
-	{
-	default: wanted_type = fci->nolen ? *(fci->nolen) : 0; break;
-	case 'H': wanted_type = fci->hhlen ? *(fci->hhlen) : 0; break;
-	case 'h': wanted_type = fci->hlen ? *(fci->hlen) : 0; break;
-	case 'l': wanted_type = fci->llen ? *(fci->llen) : 0; break;
-	case 'q': wanted_type = fci->qlen ? *(fci->qlen) : 0; break;
-	case 'L': wanted_type = fci->bigllen ? *(fci->bigllen) : 0; break;
-	case 'z': case 'Z': wanted_type = (fci->zlen
-					   ? (TYPE_DOMAIN (*fci->zlen)
-					      ? TYPE_DOMAIN (*fci->zlen)
-					      : *fci->zlen)
-					   : 0); break;
-	case 't': wanted_type = fci->tlen ? *(fci->tlen) : 0; break;
-	case 'j': wanted_type = fci->jlen ? *(fci->jlen) : 0; break;
-	}
+      wanted_type = (fci->types[length_chars_val].type
+		     ? *fci->types[length_chars_val].type : 0);
+      wanted_type_name = fci->types[length_chars_val].name;
+      wanted_type_std = fci->types[length_chars_val].std;
       if (wanted_type == 0)
-	warning ("use of `%c' length character with `%c' type character",
-		 length_char, format_char);
-      else if (length_char == 'L' && pedantic
-	       && !(format_char == 'a' || format_char == 'A'
-		    || format_char == 'e' || format_char == 'E'
-		    || format_char == 'f' || format_char == 'F'
-		    || format_char == 'g' || format_char == 'G'))
-	warning ("ISO C does not support the `L' length modifier with the `%c' type character",
-		 format_char);
-      else if (length_char == 'l'
-	       && (format_char == 'c' || format_char == 's'
-		   || format_char == '[')
-	       && pedantic && !flag_isoc94)
-	warning ("ISO C89 does not support the `l' length modifier with the `%c' type character",
-		 format_char);
-      else if (info->format_type == printf_format_type && pedantic
-	       && !flag_isoc99 && length_char == 'l'
-	       && (format_char == 'f' || format_char == 'e'
-		   || format_char == 'E' || format_char == 'g'
-		   || format_char == 'G'))
-	warning ("ISO C89 does not support the `l' length modifier with the `%c' type character",
-		 format_char);
+	warning ("use of `%s' length modifier with `%c' type character",
+		 length_chars, format_char);
+      else if (pedantic
+	       /* Warn if non-standard, provided it is more non-standard
+		  than the length and type characters that may already
+		  have been warned for.  */
+	       && wanted_type_std > length_chars_std
+	       && wanted_type_std > fci->std)
+	{
+	  if (wanted_type_std == STD_EXT)
+	    warning ("ISO C does not support the `%%%s%c' %s format",
+		     length_chars, format_char, fki->name);
+	  else if ((wanted_type_std == STD_C99 && !flag_isoc99)
+		   || (wanted_type_std == STD_C94 && !flag_isoc94))
+	    warning ("ISO C89 does not support the `%%%s%c' %s format",
+		     length_chars, format_char, fki->name);
+	}
 
       /* Finally. . .check type of argument against desired type!  */
       if (info->first_arg_num == 0)
@@ -2417,7 +2509,16 @@
 	    that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (cur_type)));
 
 	  if (strcmp (this, that) != 0)
-	    warning ("%s format, %s arg (arg %d)", this, that, arg_num);
+	    {
+	      /* There may be a better name for the format, e.g. size_t,
+		 but we should allow for programs with a perverse typedef
+		 making size_t something other than what the compiler
+		 thinks.  */
+	      if (wanted_type_name != 0
+		  && strcmp (wanted_type_name, that) != 0)
+		this = wanted_type_name;
+	      warning ("%s format, %s arg (arg %d)", this, that, arg_num);
+	    }
 	}
     }
 }
--- /dev/null	Fri Sep 11 11:31:59 1998
+++ format-diag-1.c	Fri Aug 25 00:10:43 2000
@@ -0,0 +1,18 @@
+/* Test for format diagnostics.  */
+/* Origin: Joseph Myers <jsm28@cam.ac.uk> */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu99 -Wformat" } */
+
+extern int printf (const char *, ...);
+
+void
+foo (double d)
+{
+  /* This should get a message referring to `hh', not to `H'.  */
+  printf ("%hhf", d); /* { dg-warning "hh" "%hhf warning" } */
+  /* This should get a message referring to `ll', not to `q'.  */
+  printf ("%llf", d); /* { dg-warning "ll" "%llf warning" } */
+  /* This should get a message referring to `size_t format', not to
+     `unsigned int format' or similar.  */
+  printf ("%zu", d); /* { dg-warning "size_t format" "size_t format warning" } */
+}

-- 
Joseph S. Myers
jsm28@cam.ac.uk


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]