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]
Other format: [Raw text]

[PATCH] c-format.c: cleanup of check_format_info_main


On Thu, 2016-08-04 at 14:22 -0600, Jeff Law wrote:
> On 08/04/2016 01:24 PM, David Malcolm wrote:
>
> > > Do you realize that this isn't used for ~700 lines after this
> > > point?
> > >  Is
> > > there any sensible way to factor some code here to avoid the
> > > coding
> > > disconnect.  I realize the function was huge before you got in
> > > here,
> > > but
> > > if at all possible, I'd like to see a bit of cleanup.
> > >
> > > I think this is OK after that cleanup.
> >
> > format_chars can get modified in numerous places in the intervening
> > lines, which is why I stash the value there.
> Yea, I figured that was the case.  I first noticed the stashed value,
> but didn't see where it was used for far longer than I expected.
>
> >
> > I can do some kind of cleanup of check_format_info_main, maybe
> > splitting out the things in the body of loop, moving them to
> > support
> > functions.
> That's essentially what I was thinking.
>
> >
> > That said, I note that Martin's sprintf patch:
> >   https://gcc.gnu.org/ml/gcc-patches/2016-07/msg00056.html
> > also touches those ~700 lines in check_format_info_main in over a
> > dozen
> > places.  Given that, would you prefer I do the cleanup before or
> > after
> > the substring_loc patch?
> I think you should go first with the cleanup.  It'll cause Martin
> some
> heartburn, but that happens sometimes.
>
> And FWIW, if you hadn't needed to stash away that value I probably
> wouldn't have noticed how badly that function (and the loop in
> particular) needed some refactoring.
>
> jeff

Here's a cleanup of check_format_info_main, which introduces three
new classes to hold state, and moves code from the loop into
methods of those classes, reducing the loop from ~700 lines to
~100 lines.

Unfortunately, so much changes in this patch that the before/after
diff is hard to read.  If you like the end-result, but would prefer
better history I could try to split this up into a more readable set
of patches.  (I have a version of that, but they're messy)

Successfully bootstrapped&regrtested the updated patch on
x86_64-pc-linux-gnu.

OK for trunk?

gcc/c-family/ChangeLog:
	* c-format.c (class flag_chars_t): New class.
	(struct length_modifier): New struct.
	(class argument_parser): New class.
	(flag_chars_t::flag_chars_t): New ctor.
	(flag_chars_t::has_char_p): New method.
	(flag_chars_t::add_char): New method.
	(flag_chars_t::validate): New method.
	(flag_chars_t::get_alloc_flag): New method.
	(flag_chars_t::assignment_suppression_p): New method.
	(argument_parser::argument_parser): New ctor.
	(argument_parser::read_any_dollar): New method.
	(argument_parser::read_format_flags): New method.
	(argument_parser::read_any_format_width): New method.
	(argument_parser::read_any_format_left_precision): New method.
	(argument_parser::read_any_format_precision): New method.
	(argument_parser::handle_alloc_chars): New method.
	(argument_parser::read_any_length_modifier): New method.
	(argument_parser::read_any_other_modifier): New method.
	(argument_parser::find_format_char_info): New method.
	(argument_parser::validate_flag_pairs): New method.
	(argument_parser::give_y2k_warnings): New method.
	(argument_parser::parse_any_scan_set): New method.
	(argument_parser::handle_conversions): New method.
	(argument_parser::check_argument_type): New method.
	(check_format_info_main): Introduce classes argument_parser
	and flag_chars_t, moving the code within the loop into methods
	of these classes.  Make various locals "const".
---
 gcc/c-family/c-format.c | 1655 +++++++++++++++++++++++++++++------------------
 1 file changed, 1019 insertions(+), 636 deletions(-)

diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index c19c411..92d2c1c 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -1688,740 +1688,1123 @@ check_format_arg (void *ctx, tree format_tree,
 			  params, arg_num, fwt_pool);
 }
 
+/* Support class for argument_parser and check_format_info_main.
+   Tracks any flag characters that have been applied to the
+   current argument.  */
 
-/* Do the main part of checking a call to a format function.  FORMAT_CHARS
-   is the NUL-terminated format string (which at this point may contain
-   internal NUL characters); FORMAT_LENGTH is its length (excluding the
-   terminating NUL character).  ARG_NUM is one less than the number of
-   the first format argument to check; PARAMS points to that format
-   argument in the list of arguments.  */
+class flag_chars_t
+{
+ public:
+  flag_chars_t ();
+  bool has_char_p (char ch) const;
+  void add_char (char ch);
+  void validate (const format_kind_info *fki,
+		 const format_char_info *fci,
+		 const format_flag_spec *flag_specs,
+		 const char * const format_chars,
+		 location_t format_string_loc,
+		 const char * const orig_format_chars,
+		 char format_char);
+  int get_alloc_flag (const format_kind_info *fki);
+  int assignment_suppression_p (const format_kind_info *fki);
+
+ private:
+  char m_flag_chars[256];
+};
 
-static void
-check_format_info_main (format_check_results *res,
-			function_format_info *info, const char *format_chars,
-			int format_length, tree params,
-			unsigned HOST_WIDE_INT arg_num,
-			object_allocator <format_wanted_type> &fwt_pool)
+/* Support struct for argument_parser and check_format_info_main.
+   Encapsulates any length modifier applied to the current argument.  */
+
+struct length_modifier
 {
-  const char *orig_format_chars = format_chars;
-  tree first_fillin_param = params;
+  length_modifier ()
+  : chars (NULL), val (FMT_LEN_none), std (STD_C89),
+    scalar_identity_flag (0)
+  {
+  }
 
-  const format_kind_info *fki = &format_types[info->format_type];
-  const format_flag_spec *flag_specs = fki->flag_specs;
-  const format_flag_pair *bad_flag_pairs = fki->bad_flag_pairs;
-  location_t format_string_loc = res->format_string_loc;
+  length_modifier (const char *chars_,
+		   enum format_lengths val_,
+		   enum format_std_version std_,
+		   int scalar_identity_flag_)
+  : chars (chars_), val (val_), std (std_),
+    scalar_identity_flag (scalar_identity_flag_)
+  {
+  }
 
-  /* -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.  */
-  int has_operand_number = -1;
+  const char *chars;
+  enum format_lengths val;
+  enum format_std_version std;
+  int scalar_identity_flag;
+};
 
-  init_dollar_format_checking (info->first_arg_num, first_fillin_param);
+/* Parsing one argument within a format string.  */
 
-  while (*format_chars != 0)
-    {
-      int i;
-      int suppressed = FALSE;
-      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;
-      tree cur_param;
-      tree wanted_type;
-      int main_arg_num = 0;
-      tree main_arg_params = 0;
-      enum format_std_version wanted_type_std;
-      const char *wanted_type_name;
-      format_wanted_type width_wanted_type;
-      format_wanted_type precision_wanted_type;
-      format_wanted_type main_wanted_type;
-      format_wanted_type *first_wanted_type = NULL;
-      format_wanted_type *last_wanted_type = NULL;
-      const format_length_info *fli = NULL;
-      const format_char_info *fci = NULL;
-      char flag_chars[256];
-      int alloc_flag = 0;
-      int scalar_identity_flag = 0;
-      const char *format_start;
+class argument_parser
+{
+ public:
+  argument_parser (function_format_info *info, const char *&format_chars,
+		   const char * const orig_format_chars,
+		   location_t format_string_loc, flag_chars_t &flag_chars,
+		   int &has_operand_number, tree first_fillin_param,
+		   object_allocator <format_wanted_type> &fwt_pool_);
+
+  bool read_any_dollar ();
+
+  bool read_format_flags ();
+
+  bool
+  read_any_format_width (tree &params,
+			 unsigned HOST_WIDE_INT &arg_num);
+
+  void
+  read_any_format_left_precision ();
+
+  bool
+  read_any_format_precision (tree &params,
+			     unsigned HOST_WIDE_INT &arg_num);
+
+  void handle_alloc_chars ();
+
+  length_modifier read_any_length_modifier ();
+
+  void read_any_other_modifier ();
+
+  const format_char_info *find_format_char_info (char format_char);
+
+  void
+  validate_flag_pairs (const format_char_info *fci,
+		       char format_char);
+
+  void
+  give_y2k_warnings (const format_char_info *fci,
+		     char format_char);
+
+  void parse_any_scan_set (const format_char_info *fci);
+
+  bool handle_conversions (const format_char_info *fci,
+			   const length_modifier &len_modifier,
+			   tree &wanted_type,
+			   const char *&wanted_type_name,
+			   unsigned HOST_WIDE_INT &arg_num,
+			   tree &params,
+			   char format_char);
+
+  bool
+  check_argument_type (const format_char_info *fci,
+		       const length_modifier &len_modifier,
+		       tree &wanted_type,
+		       const char *&wanted_type_name,
+		       const bool suppressed,
+		       unsigned HOST_WIDE_INT &arg_num,
+		       tree &params,
+		       const int alloc_flag,
+		       const char * const format_start);
+
+ private:
+  const function_format_info *const info;
+  const format_kind_info * const fki;
+  const format_flag_spec * const flag_specs;
+  const char *&format_chars;
+  const char * const orig_format_chars;
+  const location_t format_string_loc;
+  object_allocator <format_wanted_type> &fwt_pool;
+  flag_chars_t &flag_chars;
+  int main_arg_num;
+  tree main_arg_params;
+  int &has_operand_number;
+  const tree first_fillin_param;
+  format_wanted_type width_wanted_type;
+  format_wanted_type precision_wanted_type;
+ public:
+  format_wanted_type main_wanted_type;
+ private:
+  format_wanted_type *first_wanted_type;
+  format_wanted_type *last_wanted_type;
+};
 
-      if (*format_chars++ != '%')
+/* flag_chars_t's constructor.  */
+
+flag_chars_t::flag_chars_t ()
+{
+  m_flag_chars[0] = 0;
+}
+
+/* Has CH been seen as a flag within the current argument?  */
+
+bool
+flag_chars_t::has_char_p (char ch) const
+{
+  return strchr (m_flag_chars, ch) != 0;
+}
+
+/* Add CH to the flags seen within the current argument.  */
+
+void
+flag_chars_t::add_char (char ch)
+{
+  int i = strlen (m_flag_chars);
+  m_flag_chars[i++] = ch;
+  m_flag_chars[i] = 0;
+}
+
+/* Validate the individual flags used, removing any that are invalid.  */
+
+void
+flag_chars_t::validate (const format_kind_info *fki,
+			const format_char_info *fci,
+			const format_flag_spec *flag_specs,
+			const char * const format_chars,
+			location_t format_string_loc,
+			const char * const orig_format_chars,
+			char format_char)
+{
+  int i;
+  int d = 0;
+  for (i = 0; m_flag_chars[i] != 0; i++)
+    {
+      const format_flag_spec *s = get_flag_spec (flag_specs,
+						 m_flag_chars[i], NULL);
+      m_flag_chars[i - d] = m_flag_chars[i];
+      if (m_flag_chars[i] == fki->length_code_char)
 	continue;
-      if (*format_chars == 0)
+      if (strchr (fci->flag_chars, m_flag_chars[i]) == 0)
 	{
-          warning_at (location_from_offset (format_string_loc,
-					    format_chars - orig_format_chars),
-		      OPT_Wformat_,
-		      "spurious trailing %<%%%> in format");
+	  warning_at (location_from_offset (format_string_loc,
+					    format_chars
+					    - orig_format_chars),
+		      OPT_Wformat_, "%s used with %<%%%c%> %s format",
+		      _(s->name), format_char, fki->name);
+	  d++;
 	  continue;
 	}
-      if (*format_chars == '%')
+      if (pedantic)
+	{
+	  const format_flag_spec *t;
+	  if (ADJ_STD (s->std) > C_STD_VER)
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"%s does not support %s",
+			C_STD_NAME (s->std), _(s->long_name));
+	  t = get_flag_spec (flag_specs, m_flag_chars[i], fci->flags2);
+	  if (t != NULL && ADJ_STD (t->std) > ADJ_STD (s->std))
+	    {
+	      const char *long_name = (t->long_name != NULL
+				       ? t->long_name
+				       : s->long_name);
+	      if (ADJ_STD (t->std) > C_STD_VER)
+		warning_at (format_string_loc, OPT_Wformat_,
+			    "%s does not support %s with"
+			    " the %<%%%c%> %s format",
+			    C_STD_NAME (t->std), _(long_name),
+			    format_char, fki->name);
+	    }
+	}
+    }
+  m_flag_chars[i - d] = 0;
+}
+
+/* Determine if an assignment-allocation has been set, requiring
+   an extra char ** for writing back a dynamically-allocated char *.
+   This is for handling the optional 'm' character in scanf.  */
+
+int
+flag_chars_t::get_alloc_flag (const format_kind_info *fki)
+{
+  if ((fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+      && has_char_p ('a'))
+    return 1;
+  if (fki->alloc_char && has_char_p (fki->alloc_char))
+    return 1;
+  return 0;
+}
+
+/* Determine if an assignment-suppression character was seen.
+   ('*' in scanf, for discarding the converted input).  */
+
+int
+flag_chars_t::assignment_suppression_p (const format_kind_info *fki)
+{
+  if (fki->suppression_char
+      && has_char_p (fki->suppression_char))
+    return 1;
+  return 0;
+}
+
+/* Constructor for argument_parser.  Initialize for parsing one
+   argument within a format string.  */
+
+argument_parser::
+argument_parser (function_format_info *info_, const char *&format_chars_,
+		 const char * const orig_format_chars_,
+		 location_t format_string_loc_,
+		 flag_chars_t &flag_chars_,
+		 int &has_operand_number_,
+		 tree first_fillin_param_,
+		 object_allocator <format_wanted_type> &fwt_pool_)
+: info (info_),
+  fki (&format_types[info->format_type]),
+  flag_specs (fki->flag_specs),
+  format_chars (format_chars_),
+  orig_format_chars (orig_format_chars_),
+  format_string_loc (format_string_loc_),
+  fwt_pool (fwt_pool_),
+  flag_chars (flag_chars_),
+  main_arg_num (0),
+  main_arg_params (NULL),
+  has_operand_number (has_operand_number_),
+  first_fillin_param (first_fillin_param_),
+  first_wanted_type (NULL),
+  last_wanted_type (NULL)
+{
+}
+
+/* Handle dollars at the start of format arguments, setting up main_arg_params
+   and main_arg_num.
+
+   Return true if format parsing is to continue, false otherwise.  */
+
+bool
+argument_parser::read_any_dollar ()
+{
+  if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
+    {
+      /* Possibly read a $ operand number at the start of the format.
+	 If one was previously used, one is required here.  If one
+	 is not used here, we can't immediately conclude this is a
+	 format without them, since it could be printf %m or scanf %*.  */
+      int opnum;
+      opnum = maybe_read_dollar_number (&format_chars, 0,
+					first_fillin_param,
+					&main_arg_params, fki);
+      if (opnum == -1)
+	return false;
+      else if (opnum > 0)
+	{
+	  has_operand_number = 1;
+	  main_arg_num = opnum + info->first_arg_num - 1;
+	}
+    }
+  else if (fki->flags & FMT_FLAG_USE_DOLLAR)
+    {
+      if (avoid_dollar_number (format_chars))
+	return false;
+    }
+  return true;
+}
+
+/* Read any format flags, but do not yet validate them beyond removing
+   duplicates, since in general validation depends on the rest of
+   the format.
+
+   Return true if format parsing is to continue, false otherwise.  */
+
+bool
+argument_parser::read_format_flags ()
+{
+  while (*format_chars != 0
+	 && strchr (fki->flag_chars, *format_chars) != 0)
+    {
+      const format_flag_spec *s = get_flag_spec (flag_specs,
+						 *format_chars, NULL);
+      if (flag_chars.has_char_p (*format_chars))
+	{
+	  warning_at (location_from_offset (format_string_loc,
+					    format_chars + 1
+					    - orig_format_chars),
+		      OPT_Wformat_,
+		      "repeated %s in format", _(s->name));
+	}
+      else
+	flag_chars.add_char (*format_chars);
+
+      if (s->skip_next_char)
 	{
 	  ++format_chars;
-	  continue;
+	  if (*format_chars == 0)
+	    {
+	      warning_at (format_string_loc, OPT_Wformat_,
+			  "missing fill character at end of strfmon format");
+	      return false;
+	    }
 	}
-      flag_chars[0] = 0;
+      ++format_chars;
+    }
+
+  return true;
+}
+
+/* Read any format width, possibly * or *m$.
+
+   Return true if format parsing is to continue, false otherwise.  */
+
+bool
+argument_parser::
+read_any_format_width (tree &params,
+		       unsigned HOST_WIDE_INT &arg_num)
+{
+  if (!fki->width_char)
+    return true;
 
-      if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
+  if (fki->width_type != NULL && *format_chars == '*')
+    {
+      flag_chars.add_char (fki->width_char);
+      /* "...a field width...may be indicated by an asterisk.
+	 In this case, an int argument supplies the field width..."  */
+      ++format_chars;
+      if (has_operand_number != 0)
 	{
-	  /* Possibly read a $ operand number at the start of the format.
-	     If one was previously used, one is required here.  If one
-	     is not used here, we can't immediately conclude this is a
-	     format without them, since it could be printf %m or scanf %*.  */
 	  int opnum;
-	  opnum = maybe_read_dollar_number (&format_chars, 0,
+	  opnum = maybe_read_dollar_number (&format_chars,
+					    has_operand_number == 1,
 					    first_fillin_param,
-					    &main_arg_params, fki);
+					    &params, fki);
 	  if (opnum == -1)
-	    return;
+	    return false;
 	  else if (opnum > 0)
 	    {
 	      has_operand_number = 1;
-	      main_arg_num = opnum + info->first_arg_num - 1;
+	      arg_num = opnum + info->first_arg_num - 1;
 	    }
+	  else
+	    has_operand_number = 0;
 	}
-      else if (fki->flags & FMT_FLAG_USE_DOLLAR)
+      else
 	{
 	  if (avoid_dollar_number (format_chars))
-	    return;
+	    return false;
 	}
-
-      /* Read any format flags, but do not yet validate them beyond removing
-	 duplicates, since in general validation depends on the rest of
-	 the format.  */
-      while (*format_chars != 0
-	     && strchr (fki->flag_chars, *format_chars) != 0)
+      if (info->first_arg_num != 0)
 	{
-	  const format_flag_spec *s = get_flag_spec (flag_specs,
-						     *format_chars, NULL);
-	  if (strchr (flag_chars, *format_chars) != 0)
-	    {
-	      warning_at (location_from_offset (format_string_loc,
-						format_chars + 1
-						- orig_format_chars),
-			  OPT_Wformat_,
-			  "repeated %s in format", _(s->name));
-	    }
+	  tree cur_param;
+	  if (params == 0)
+	    cur_param = NULL;
 	  else
 	    {
-	      i = strlen (flag_chars);
-	      flag_chars[i++] = *format_chars;
-	      flag_chars[i] = 0;
-	    }
-	  if (s->skip_next_char)
-	    {
-	      ++format_chars;
-	      if (*format_chars == 0)
+	      cur_param = TREE_VALUE (params);
+	      if (has_operand_number <= 0)
 		{
-		  warning_at (format_string_loc, OPT_Wformat_,
-			      "missing fill character at end of strfmon format");
-		  return;
+		  params = TREE_CHAIN (params);
+		  ++arg_num;
 		}
 	    }
+	  width_wanted_type.wanted_type = *fki->width_type;
+	  width_wanted_type.wanted_type_name = NULL;
+	  width_wanted_type.pointer_count = 0;
+	  width_wanted_type.char_lenient_flag = 0;
+	  width_wanted_type.scalar_identity_flag = 0;
+	  width_wanted_type.writing_in_flag = 0;
+	  width_wanted_type.reading_from_flag = 0;
+	  width_wanted_type.kind = CF_KIND_FIELD_WIDTH;
+	  width_wanted_type.format_start = format_chars - 1;
+	  width_wanted_type.format_length = 1;
+	  width_wanted_type.param = cur_param;
+	  width_wanted_type.arg_num = arg_num;
+	  width_wanted_type.offset_loc =
+	    format_chars - orig_format_chars;
+	  width_wanted_type.next = NULL;
+	  if (last_wanted_type != 0)
+	    last_wanted_type->next = &width_wanted_type;
+	  if (first_wanted_type == 0)
+	    first_wanted_type = &width_wanted_type;
+	  last_wanted_type = &width_wanted_type;
+	}
+    }
+  else
+    {
+      /* Possibly read a numeric width.  If the width is zero,
+	 we complain if appropriate.  */
+      int non_zero_width_char = FALSE;
+      int found_width = FALSE;
+      while (ISDIGIT (*format_chars))
+	{
+	  found_width = TRUE;
+	  if (*format_chars != '0')
+	    non_zero_width_char = TRUE;
 	  ++format_chars;
 	}
+      if (found_width && !non_zero_width_char &&
+	  (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
+	warning_at (format_string_loc, OPT_Wformat_,
+		    "zero width in %s format", fki->name);
+      if (found_width)
+	flag_chars.add_char (fki->width_char);
+    }
 
-      /* Read any format width, possibly * or *m$.  */
-      if (fki->width_char != 0)
+  return true;
+}
+
+/* Read any format left precision (must be a number, not *).  */
+void
+argument_parser::read_any_format_left_precision ()
+{
+  if (fki->left_precision_char == 0)
+    return;
+  if (*format_chars != '#')
+    return;
+
+  ++format_chars;
+  flag_chars.add_char (fki->left_precision_char);
+  if (!ISDIGIT (*format_chars))
+    warning_at (location_from_offset (format_string_loc,
+				      format_chars - orig_format_chars),
+		OPT_Wformat_,
+		"empty left precision in %s format", fki->name);
+  while (ISDIGIT (*format_chars))
+    ++format_chars;
+}
+
+/* Read any format precision, possibly * or *m$.
+
+   Return true if format parsing is to continue, false otherwise.  */
+
+bool
+argument_parser::
+read_any_format_precision (tree &params,
+			   unsigned HOST_WIDE_INT &arg_num)
+{
+  if (fki->precision_char == 0)
+    return true;
+  if (*format_chars != '.')
+    return true;
+
+  ++format_chars;
+  flag_chars.add_char (fki->precision_char);
+  if (fki->precision_type != NULL && *format_chars == '*')
+    {
+      /* "...a...precision...may be indicated by an asterisk.
+	 In this case, an int argument supplies the...precision."  */
+      ++format_chars;
+      if (has_operand_number != 0)
 	{
-	  if (fki->width_type != NULL && *format_chars == '*')
+	  int opnum;
+	  opnum = maybe_read_dollar_number (&format_chars,
+					    has_operand_number == 1,
+					    first_fillin_param,
+					    &params, fki);
+	  if (opnum == -1)
+	    return false;
+	  else if (opnum > 0)
 	    {
-	      i = strlen (flag_chars);
-	      flag_chars[i++] = fki->width_char;
-	      flag_chars[i] = 0;
-	      /* "...a field width...may be indicated by an asterisk.
-		 In this case, an int argument supplies the field width..."  */
-	      ++format_chars;
-	      if (has_operand_number != 0)
-		{
-		  int opnum;
-		  opnum = maybe_read_dollar_number (&format_chars,
-						    has_operand_number == 1,
-						    first_fillin_param,
-						    &params, fki);
-		  if (opnum == -1)
-		    return;
-		  else if (opnum > 0)
-		    {
-		      has_operand_number = 1;
-		      arg_num = opnum + info->first_arg_num - 1;
-		    }
-		  else
-		    has_operand_number = 0;
-		}
-	      else
-		{
-		  if (avoid_dollar_number (format_chars))
-		    return;
-		}
-	      if (info->first_arg_num != 0)
-		{
-		  if (params == 0)
-                    cur_param = NULL;
-                  else
-                    {
-                      cur_param = TREE_VALUE (params);
-                      if (has_operand_number <= 0)
-                        {
-                          params = TREE_CHAIN (params);
-                          ++arg_num;
-                        }
-                    }
-		  width_wanted_type.wanted_type = *fki->width_type;
-		  width_wanted_type.wanted_type_name = NULL;
-		  width_wanted_type.pointer_count = 0;
-		  width_wanted_type.char_lenient_flag = 0;
-		  width_wanted_type.scalar_identity_flag = 0;
-		  width_wanted_type.writing_in_flag = 0;
-		  width_wanted_type.reading_from_flag = 0;
-                  width_wanted_type.kind = CF_KIND_FIELD_WIDTH;
-		  width_wanted_type.format_start = format_chars - 1;
-		  width_wanted_type.format_length = 1;
-		  width_wanted_type.param = cur_param;
-		  width_wanted_type.arg_num = arg_num;
-		  width_wanted_type.offset_loc =
-		    format_chars - orig_format_chars;
-		  width_wanted_type.next = NULL;
-		  if (last_wanted_type != 0)
-		    last_wanted_type->next = &width_wanted_type;
-		  if (first_wanted_type == 0)
-		    first_wanted_type = &width_wanted_type;
-		  last_wanted_type = &width_wanted_type;
-		}
+	      has_operand_number = 1;
+	      arg_num = opnum + info->first_arg_num - 1;
 	    }
 	  else
-	    {
-	      /* Possibly read a numeric width.  If the width is zero,
-		 we complain if appropriate.  */
-	      int non_zero_width_char = FALSE;
-	      int found_width = FALSE;
-	      while (ISDIGIT (*format_chars))
-		{
-		  found_width = TRUE;
-		  if (*format_chars != '0')
-		    non_zero_width_char = TRUE;
-		  ++format_chars;
-		}
-	      if (found_width && !non_zero_width_char &&
-		  (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "zero width in %s format", fki->name);
-	      if (found_width)
-		{
-		  i = strlen (flag_chars);
-		  flag_chars[i++] = fki->width_char;
-		  flag_chars[i] = 0;
-		}
-	    }
+	    has_operand_number = 0;
 	}
-
-      /* Read any format left precision (must be a number, not *).  */
-      if (fki->left_precision_char != 0 && *format_chars == '#')
+      else
 	{
-	  ++format_chars;
-	  i = strlen (flag_chars);
-	  flag_chars[i++] = fki->left_precision_char;
-	  flag_chars[i] = 0;
-	  if (!ISDIGIT (*format_chars))
-	    warning_at (location_from_offset (format_string_loc,
-					      format_chars - orig_format_chars),
-			OPT_Wformat_,
-			"empty left precision in %s format", fki->name);
-	  while (ISDIGIT (*format_chars))
-	    ++format_chars;
+	  if (avoid_dollar_number (format_chars))
+	    return false;
 	}
-
-      /* Read any format precision, possibly * or *m$.  */
-      if (fki->precision_char != 0 && *format_chars == '.')
+      if (info->first_arg_num != 0)
 	{
-	  ++format_chars;
-	  i = strlen (flag_chars);
-	  flag_chars[i++] = fki->precision_char;
-	  flag_chars[i] = 0;
-	  if (fki->precision_type != NULL && *format_chars == '*')
+	  tree cur_param;
+	  if (params == 0)
+	    cur_param = NULL;
+	  else
 	    {
-	      /* "...a...precision...may be indicated by an asterisk.
-		 In this case, an int argument supplies the...precision."  */
-	      ++format_chars;
-	      if (has_operand_number != 0)
+	      cur_param = TREE_VALUE (params);
+	      if (has_operand_number <= 0)
 		{
-		  int opnum;
-		  opnum = maybe_read_dollar_number (&format_chars,
-						    has_operand_number == 1,
-						    first_fillin_param,
-						    &params, fki);
-		  if (opnum == -1)
-		    return;
-		  else if (opnum > 0)
-		    {
-		      has_operand_number = 1;
-		      arg_num = opnum + info->first_arg_num - 1;
-		    }
-		  else
-		    has_operand_number = 0;
-		}
-	      else
-		{
-		  if (avoid_dollar_number (format_chars))
-		    return;
-		}
-	      if (info->first_arg_num != 0)
-		{
-		  if (params == 0)
-                    cur_param = NULL;
-                  else
-                    {
-                      cur_param = TREE_VALUE (params);
-                      if (has_operand_number <= 0)
-                        {
-                          params = TREE_CHAIN (params);
-                          ++arg_num;
-                        }
-                    }
-		  precision_wanted_type.wanted_type = *fki->precision_type;
-		  precision_wanted_type.wanted_type_name = NULL;
-		  precision_wanted_type.pointer_count = 0;
-		  precision_wanted_type.char_lenient_flag = 0;
-		  precision_wanted_type.scalar_identity_flag = 0;
-		  precision_wanted_type.writing_in_flag = 0;
-		  precision_wanted_type.reading_from_flag = 0;
-                  precision_wanted_type.kind = CF_KIND_FIELD_PRECISION;
-		  precision_wanted_type.param = cur_param;
-		  precision_wanted_type.format_start = format_chars - 2;
-		  precision_wanted_type.format_length = 2;
-		  precision_wanted_type.arg_num = arg_num;
-		  precision_wanted_type.offset_loc =
-		    format_chars - orig_format_chars;
-		  precision_wanted_type.next = NULL;
-		  if (last_wanted_type != 0)
-		    last_wanted_type->next = &precision_wanted_type;
-		  if (first_wanted_type == 0)
-		    first_wanted_type = &precision_wanted_type;
-		  last_wanted_type = &precision_wanted_type;
+		  params = TREE_CHAIN (params);
+		  ++arg_num;
 		}
 	    }
-	  else
-	    {
-	      if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
-		  && !ISDIGIT (*format_chars))
-		warning_at (location_from_offset (format_string_loc,
-						  format_chars - orig_format_chars),
-			    OPT_Wformat_,
-			    "empty precision in %s format", fki->name);
-	      while (ISDIGIT (*format_chars))
-		++format_chars;
-	    }
+	  precision_wanted_type.wanted_type = *fki->precision_type;
+	  precision_wanted_type.wanted_type_name = NULL;
+	  precision_wanted_type.pointer_count = 0;
+	  precision_wanted_type.char_lenient_flag = 0;
+	  precision_wanted_type.scalar_identity_flag = 0;
+	  precision_wanted_type.writing_in_flag = 0;
+	  precision_wanted_type.reading_from_flag = 0;
+	  precision_wanted_type.kind = CF_KIND_FIELD_PRECISION;
+	  precision_wanted_type.param = cur_param;
+	  precision_wanted_type.format_start = format_chars - 2;
+	  precision_wanted_type.format_length = 2;
+	  precision_wanted_type.arg_num = arg_num;
+	  precision_wanted_type.offset_loc =
+	    format_chars - orig_format_chars;
+	  precision_wanted_type.next = NULL;
+	  if (last_wanted_type != 0)
+	    last_wanted_type->next = &precision_wanted_type;
+	  if (first_wanted_type == 0)
+	    first_wanted_type = &precision_wanted_type;
+	  last_wanted_type = &precision_wanted_type;
 	}
+    }
+  else
+    {
+      if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
+	  && !ISDIGIT (*format_chars))
+	warning_at (location_from_offset (format_string_loc,
+					  format_chars - orig_format_chars),
+		    OPT_Wformat_,
+		    "empty precision in %s format", fki->name);
+      while (ISDIGIT (*format_chars))
+	++format_chars;
+    }
 
-      format_start = format_chars;
-      if (fki->alloc_char && fki->alloc_char == *format_chars)
-	{
-	  i = strlen (flag_chars);
-	  flag_chars[i++] = fki->alloc_char;
-	  flag_chars[i] = 0;
-	  format_chars++;
-	}
+  return true;
+}
 
-      /* Handle the scanf allocation kludge.  */
-      if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+/* Parse any assignment-allocation flags, which request an extra
+   char ** for writing back a dynamically-allocated char *.
+   This is for handling the optional 'm' character in scanf,
+   and, before C99, 'a' (for compatibility with a non-standard
+   GNU libc extension).  */
+
+void
+argument_parser::handle_alloc_chars ()
+{
+  if (fki->alloc_char && fki->alloc_char == *format_chars)
+    {
+      flag_chars.add_char (fki->alloc_char);
+      format_chars++;
+    }
+
+  /* Handle the scanf allocation kludge.  */
+  if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
+    {
+      if (*format_chars == 'a' && !flag_isoc99)
 	{
-	  if (*format_chars == 'a' && !flag_isoc99)
+	  if (format_chars[1] == 's' || format_chars[1] == 'S'
+	      || format_chars[1] == '[')
 	    {
-	      if (format_chars[1] == 's' || format_chars[1] == 'S'
-		  || format_chars[1] == '[')
-		{
-		  /* 'a' is used as a flag.  */
-		  i = strlen (flag_chars);
-		  flag_chars[i++] = 'a';
-		  flag_chars[i] = 0;
-		  format_chars++;
-		}
+	      /* 'a' is used as a flag.  */
+	      flag_chars.add_char ('a');
+	      format_chars++;
 	    }
 	}
+    }
+}
 
-      /* Read any length modifier, if this kind of format has them.  */
-      fli = fki->length_char_specs;
-      length_chars = NULL;
-      length_chars_val = FMT_LEN_none;
-      length_chars_std = STD_C89;
-      scalar_identity_flag = 0;
-      if (fli)
+/* Look for length modifiers within the current format argument,
+   returning a length_modifier instance describing it (or the
+   default if one is not found).
+
+   Issue warnings about non-standard modifiers.  */
+
+length_modifier
+argument_parser::read_any_length_modifier ()
+{
+  length_modifier result;
+
+  const format_length_info *fli = fki->length_char_specs;
+  if (!fli)
+    return result;
+
+  while (fli->name != 0
+	 && strncmp (fli->name, format_chars, strlen (fli->name)))
+    fli++;
+  if (fli->name != 0)
+    {
+      format_chars += strlen (fli->name);
+      if (fli->double_name != 0 && fli->name[0] == *format_chars)
 	{
-	  while (fli->name != 0
- 		 && strncmp (fli->name, format_chars, strlen (fli->name)))
-	      fli++;
-	  if (fli->name != 0)
-	    {
- 	      format_chars += strlen (fli->name);
-	      if (fli->double_name != 0 && fli->name[0] == *format_chars)
-		{
-		  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;
-		  scalar_identity_flag = fli->scalar_identity_flag;
-		}
-	      i = strlen (flag_chars);
-	      flag_chars[i++] = fki->length_code_char;
-	      flag_chars[i] = 0;
-	    }
-	  if (pedantic)
-	    {
-	      /* Warn if the length modifier is non-standard.  */
-	      if (ADJ_STD (length_chars_std) > C_STD_VER)
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "%s does not support the %qs %s length modifier",
-			    C_STD_NAME (length_chars_std), length_chars,
-			    fki->name);
-	    }
+	  format_chars++;
+	  result = length_modifier (fli->double_name, fli->double_index,
+				    fli->double_std, 0);
 	}
-
-      /* Read any modifier (strftime E/O).  */
-      if (fki->modifier_chars != NULL)
+      else
 	{
-	  while (*format_chars != 0
-		 && strchr (fki->modifier_chars, *format_chars) != 0)
-	    {
-	      if (strchr (flag_chars, *format_chars) != 0)
-		{
-		  const format_flag_spec *s = get_flag_spec (flag_specs,
-							     *format_chars, NULL);
-		  warning_at (location_from_offset (format_string_loc,
-						    format_chars 
-						    - orig_format_chars),
-			      OPT_Wformat_,
-			      "repeated %s in format", _(s->name));
-		}
-	      else
-		{
-		  i = strlen (flag_chars);
-		  flag_chars[i++] = *format_chars;
-		  flag_chars[i] = 0;
-		}
-	      ++format_chars;
-	    }
+	  result = length_modifier (fli->name, fli->index, fli->std,
+				    fli->scalar_identity_flag);
 	}
+      flag_chars.add_char (fki->length_code_char);
+    }
+  if (pedantic)
+    {
+      /* Warn if the length modifier is non-standard.  */
+      if (ADJ_STD (result.std) > C_STD_VER)
+	warning_at (format_string_loc, OPT_Wformat_,
+		    "%s does not support the %qs %s length modifier",
+		    C_STD_NAME (result.std), result.chars,
+		    fki->name);
+    }
 
-      format_char = *format_chars;
-      if (format_char == 0
-	  || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
-	      && format_char == '%'))
+  return result;
+}
+
+/* Read any other modifier (strftime E/O).  */
+
+void
+argument_parser::read_any_other_modifier ()
+{
+  if (fki->modifier_chars == NULL)
+    return;
+
+  while (*format_chars != 0
+	 && strchr (fki->modifier_chars, *format_chars) != 0)
+    {
+      if (flag_chars.has_char_p (*format_chars))
 	{
+	  const format_flag_spec *s = get_flag_spec (flag_specs,
+						     *format_chars, NULL);
 	  warning_at (location_from_offset (format_string_loc,
-					    format_chars - orig_format_chars),
+					    format_chars
+					    - orig_format_chars),
 		      OPT_Wformat_,
-		      "conversion lacks type at end of format");
-	  continue;
+		      "repeated %s in format", _(s->name));
 	}
-      format_chars++;
-      fci = fki->conversion_specs;
-      while (fci->format_chars != 0
-	     && strchr (fci->format_chars, format_char) == 0)
-	  ++fci;
-      if (fci->format_chars == 0)
+      else
+	flag_chars.add_char (*format_chars);
+      ++format_chars;
+    }
+}
+
+/* Return the format_char_info corresponding to FORMAT_CHAR,
+   potentially issuing a warning if the format char is
+   not supported in the C standard version we are checking
+   against.
+
+   Issue a warning and return NULL if it is not found.
+
+   Issue warnings about non-standard modifiers.  */
+
+const format_char_info *
+argument_parser::find_format_char_info (char format_char)
+{
+  const format_char_info *fci = fki->conversion_specs;
+
+  while (fci->format_chars != 0
+	 && strchr (fci->format_chars, format_char) == 0)
+    ++fci;
+  if (fci->format_chars == 0)
+    {
+      if (ISGRAPH (format_char))
+	warning_at (location_from_offset (format_string_loc,
+					  format_chars - orig_format_chars),
+		    OPT_Wformat_,
+		    "unknown conversion type character %qc in format",
+		    format_char);
+      else
+	warning_at (location_from_offset (format_string_loc,
+					  format_chars - orig_format_chars),
+		    OPT_Wformat_,
+		    "unknown conversion type character 0x%x in format",
+		    format_char);
+      return NULL;
+    }
+
+  if (pedantic)
+    {
+      if (ADJ_STD (fci->std) > C_STD_VER)
+	warning_at (location_from_offset (format_string_loc,
+					  format_chars - orig_format_chars),
+		    OPT_Wformat_,
+		    "%s does not support the %<%%%c%> %s format",
+		    C_STD_NAME (fci->std), format_char, fki->name);
+    }
+
+  return fci;
+}
+
+/* Validate the pairs of flags used.
+   Issue warnings about incompatible combinations of flags.  */
+
+void
+argument_parser::validate_flag_pairs (const format_char_info *fci,
+				      char format_char)
+{
+  const format_flag_pair * const bad_flag_pairs = fki->bad_flag_pairs;
+
+  for (int i = 0; bad_flag_pairs[i].flag_char1 != 0; i++)
+    {
+      const format_flag_spec *s, *t;
+      if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char1))
+	continue;
+      if (!flag_chars.has_char_p (bad_flag_pairs[i].flag_char2))
+	continue;
+      if (bad_flag_pairs[i].predicate != 0
+	  && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0)
+	continue;
+      s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL);
+      t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL);
+      if (bad_flag_pairs[i].ignored)
 	{
-	  if (ISGRAPH (format_char))
-	    warning_at (location_from_offset (format_string_loc,
-					      format_chars - orig_format_chars),
-			OPT_Wformat_,
-			"unknown conversion type character %qc in format",
-			format_char);
+	  if (bad_flag_pairs[i].predicate != 0)
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"%s ignored with %s and %<%%%c%> %s format",
+			_(s->name), _(t->name), format_char,
+			fki->name);
 	  else
-	    warning_at (location_from_offset (format_string_loc,
-					      format_chars - orig_format_chars),
-			OPT_Wformat_,
-			"unknown conversion type character 0x%x in format",
-			format_char);
-	  continue;
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"%s ignored with %s in %s format",
+			_(s->name), _(t->name), fki->name);
 	}
-      if (pedantic)
+      else
 	{
-	  if (ADJ_STD (fci->std) > C_STD_VER)
-	    warning_at (location_from_offset (format_string_loc,
-					      format_chars - orig_format_chars),
-			OPT_Wformat_,
-			"%s does not support the %<%%%c%> %s format",
-			C_STD_NAME (fci->std), format_char, fki->name);
+	  if (bad_flag_pairs[i].predicate != 0)
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"use of %s and %s together with %<%%%c%> %s format",
+			_(s->name), _(t->name), format_char,
+			fki->name);
+	  else
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"use of %s and %s together in %s format",
+			_(s->name), _(t->name), fki->name);
 	}
+    }
+}
 
-      /* Validate the individual flags used, removing any that are invalid.  */
-      {
-	int d = 0;
-	for (i = 0; flag_chars[i] != 0; i++)
-	  {
-	    const format_flag_spec *s = get_flag_spec (flag_specs,
-						       flag_chars[i], NULL);
-	    flag_chars[i - d] = flag_chars[i];
-	    if (flag_chars[i] == fki->length_code_char)
-	      continue;
-	    if (strchr (fci->flag_chars, flag_chars[i]) == 0)
-	      {
-		warning_at (location_from_offset (format_string_loc,
-						  format_chars 
-						  - orig_format_chars),
-			    OPT_Wformat_, "%s used with %<%%%c%> %s format",
-			    _(s->name), format_char, fki->name);
-		d++;
-		continue;
-	      }
-	    if (pedantic)
-	      {
-		const format_flag_spec *t;
-		if (ADJ_STD (s->std) > C_STD_VER)
-		  warning_at (format_string_loc, OPT_Wformat_,
-			      "%s does not support %s",
-                              C_STD_NAME (s->std), _(s->long_name));
-		t = get_flag_spec (flag_specs, flag_chars[i], fci->flags2);
-		if (t != NULL && ADJ_STD (t->std) > ADJ_STD (s->std))
-		  {
-		    const char *long_name = (t->long_name != NULL
-					     ? t->long_name
-					     : s->long_name);
-		    if (ADJ_STD (t->std) > C_STD_VER)
-		      warning_at (format_string_loc, OPT_Wformat_,
-				  "%s does not support %s with the %<%%%c%> %s format",
-				  C_STD_NAME (t->std), _(long_name),
-				  format_char, fki->name);
-		  }
-	      }
-	  }
-	flag_chars[i - d] = 0;
-      }
-
-      if ((fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
-	  && strchr (flag_chars, 'a') != 0)
-	alloc_flag = 1;
-      if (fki->alloc_char && strchr (flag_chars, fki->alloc_char) != 0)
-	alloc_flag = 1;
-
-      if (fki->suppression_char
-	  && strchr (flag_chars, fki->suppression_char) != 0)
-	suppressed = 1;
+/* Give Y2K warnings.  */
 
-      /* Validate the pairs of flags used.  */
-      for (i = 0; bad_flag_pairs[i].flag_char1 != 0; i++)
+void
+argument_parser::give_y2k_warnings (const format_char_info *fci,
+				    char format_char)
+{
+  if (!warn_format_y2k)
+    return;
+
+  int y2k_level = 0;
+  if (strchr (fci->flags2, '4') != 0)
+    if (flag_chars.has_char_p ('E'))
+      y2k_level = 3;
+    else
+      y2k_level = 2;
+  else if (strchr (fci->flags2, '3') != 0)
+    y2k_level = 3;
+  else if (strchr (fci->flags2, '2') != 0)
+    y2k_level = 2;
+  if (y2k_level == 3)
+    warning_at (format_string_loc, OPT_Wformat_y2k,
+		"%<%%%c%> yields only last 2 digits of "
+		"year in some locales", format_char);
+  else if (y2k_level == 2)
+    warning_at (format_string_loc, OPT_Wformat_y2k,
+		"%<%%%c%> yields only last 2 digits of year",
+		format_char);
+}
+
+/* Parse any "scan sets" enclosed in square brackets, e.g.
+   for scanf-style calls.  */
+
+void
+argument_parser::parse_any_scan_set (const format_char_info *fci)
+{
+  if (strchr (fci->flags2, '[') == NULL)
+    return;
+
+  /* Skip over scan set, in case it happens to have '%' in it.  */
+  if (*format_chars == '^')
+    ++format_chars;
+  /* Find closing bracket; if one is hit immediately, then
+     it's part of the scan set rather than a terminator.  */
+  if (*format_chars == ']')
+    ++format_chars;
+  while (*format_chars && *format_chars != ']')
+    ++format_chars;
+  if (*format_chars != ']')
+    /* The end of the format string was reached.  */
+    warning_at (location_from_offset (format_string_loc,
+				      format_chars - orig_format_chars),
+		OPT_Wformat_,
+		"no closing %<]%> for %<%%[%> format");
+}
+
+/* Return true if this argument is to be continued to be parsed,
+   false to skip to next argument.  */
+
+bool
+argument_parser::handle_conversions (const format_char_info *fci,
+				     const length_modifier &len_modifier,
+				     tree &wanted_type,
+				     const char *&wanted_type_name,
+				     unsigned HOST_WIDE_INT &arg_num,
+				     tree &params,
+				     char format_char)
+{
+  enum format_std_version wanted_type_std;
+
+  if (!(fki->flags & (int) FMT_FLAG_ARG_CONVERT))
+    return true;
+
+  wanted_type = (fci->types[len_modifier.val].type
+		 ? *fci->types[len_modifier.val].type : 0);
+  wanted_type_name = fci->types[len_modifier.val].name;
+  wanted_type_std = fci->types[len_modifier.val].std;
+  if (wanted_type == 0)
+    {
+      warning_at (location_from_offset (format_string_loc,
+					format_chars - orig_format_chars),
+		  OPT_Wformat_,
+		  "use of %qs length modifier with %qc type character"
+		  " has either no effect or undefined behavior",
+		  len_modifier.chars, format_char);
+      /* Heuristic: skip one argument when an invalid length/type
+	 combination is encountered.  */
+      arg_num++;
+      if (params != 0)
+	params = TREE_CHAIN (params);
+      return false;
+    }
+  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.  */
+	   && ADJ_STD (wanted_type_std) > ADJ_STD (len_modifier.std)
+	   && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std))
+    {
+      if (ADJ_STD (wanted_type_std) > C_STD_VER)
+	warning_at (location_from_offset (format_string_loc,
+					  format_chars - orig_format_chars),
+		    OPT_Wformat_,
+		    "%s does not support the %<%%%s%c%> %s format",
+		    C_STD_NAME (wanted_type_std), len_modifier.chars,
+		    format_char, fki->name);
+    }
+
+  return true;
+}
+
+/* Check type of argument against desired type.
+
+   Return true if format parsing is to continue, false otherwise.  */
+
+bool
+argument_parser::
+check_argument_type (const format_char_info *fci,
+		     const length_modifier &len_modifier,
+		     tree &wanted_type,
+		     const char *&wanted_type_name,
+		     const bool suppressed,
+		     unsigned HOST_WIDE_INT &arg_num,
+		     tree &params,
+		     const int alloc_flag,
+		     const char * const format_start)
+{
+  if (info->first_arg_num == 0)
+    return true;
+
+  if ((fci->pointer_count == 0 && wanted_type == void_type_node)
+      || suppressed)
+    {
+      if (main_arg_num != 0)
 	{
-	  const format_flag_spec *s, *t;
-	  if (strchr (flag_chars, bad_flag_pairs[i].flag_char1) == 0)
-	    continue;
-	  if (strchr (flag_chars, bad_flag_pairs[i].flag_char2) == 0)
-	    continue;
-	  if (bad_flag_pairs[i].predicate != 0
-	      && strchr (fci->flags2, bad_flag_pairs[i].predicate) == 0)
-	    continue;
-	  s = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char1, NULL);
-	  t = get_flag_spec (flag_specs, bad_flag_pairs[i].flag_char2, NULL);
-	  if (bad_flag_pairs[i].ignored)
-	    {
-	      if (bad_flag_pairs[i].predicate != 0)
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "%s ignored with %s and %<%%%c%> %s format",
-			    _(s->name), _(t->name), format_char,
-			    fki->name);
-	      else
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "%s ignored with %s in %s format",
-			    _(s->name), _(t->name), fki->name);
-	    }
+	  if (suppressed)
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"operand number specified with "
+			"suppressed assignment");
 	  else
-	    {
-	      if (bad_flag_pairs[i].predicate != 0)
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "use of %s and %s together with %<%%%c%> %s format",
-			    _(s->name), _(t->name), format_char,
-			    fki->name);
-	      else
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "use of %s and %s together in %s format",
-			    _(s->name), _(t->name), fki->name);
-	    }
+	    warning_at (format_string_loc, OPT_Wformat_,
+			"operand number specified for format "
+			"taking no argument");
 	}
+    }
+  else
+    {
+      format_wanted_type *wanted_type_ptr;
 
-      /* Give Y2K warnings.  */
-      if (warn_format_y2k)
+      if (main_arg_num != 0)
 	{
-	  int y2k_level = 0;
-	  if (strchr (fci->flags2, '4') != 0)
-	    if (strchr (flag_chars, 'E') != 0)
-	      y2k_level = 3;
-	    else
-	      y2k_level = 2;
-	  else if (strchr (fci->flags2, '3') != 0)
-	    y2k_level = 3;
-	  else if (strchr (fci->flags2, '2') != 0)
-	    y2k_level = 2;
-	  if (y2k_level == 3)
-	    warning_at (format_string_loc, OPT_Wformat_y2k,
-			"%<%%%c%> yields only last 2 digits of "
-			"year in some locales", format_char);
-	  else if (y2k_level == 2)
-	    warning_at (format_string_loc, OPT_Wformat_y2k,
-			"%<%%%c%> yields only last 2 digits of year",
-			format_char);
+	  arg_num = main_arg_num;
+	  params = main_arg_params;
 	}
-
-      if (strchr (fci->flags2, '[') != 0)
+      else
 	{
-	  /* Skip over scan set, in case it happens to have '%' in it.  */
-	  if (*format_chars == '^')
-	    ++format_chars;
-	  /* Find closing bracket; if one is hit immediately, then
-	     it's part of the scan set rather than a terminator.  */
-	  if (*format_chars == ']')
-	    ++format_chars;
-	  while (*format_chars && *format_chars != ']')
-	    ++format_chars;
-	  if (*format_chars != ']')
-	    /* The end of the format string was reached.  */
-	    warning_at (location_from_offset (format_string_loc,
-					      format_chars - orig_format_chars),
-			OPT_Wformat_,
-			"no closing %<]%> for %<%%[%> format");
+	  ++arg_num;
+	  if (has_operand_number > 0)
+	    {
+	      warning_at (format_string_loc, OPT_Wformat_,
+			  "missing $ operand number in format");
+	      return false;
+	    }
+	  else
+	    has_operand_number = 0;
 	}
 
-      wanted_type = 0;
-      wanted_type_name = 0;
-      if (fki->flags & (int) FMT_FLAG_ARG_CONVERT)
+      wanted_type_ptr = &main_wanted_type;
+      while (fci)
 	{
-	  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)
+	  tree cur_param;
+	  if (params == 0)
+	    cur_param = NULL;
+	  else
 	    {
-	      warning_at (location_from_offset (format_string_loc,
-						format_chars - orig_format_chars),
-			  OPT_Wformat_,
-			  "use of %qs length modifier with %qc type character"
-			  " has either no effect or undefined behavior",
-			  length_chars, format_char);
-	      /* Heuristic: skip one argument when an invalid length/type
-		 combination is encountered.  */
-	      arg_num++;
-	      if (params != 0)
-                params = TREE_CHAIN (params);
-	      continue;
+	      cur_param = TREE_VALUE (params);
+	      params = TREE_CHAIN (params);
 	    }
-	  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.  */
-		   && ADJ_STD (wanted_type_std) > ADJ_STD (length_chars_std)
-		   && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std))
+
+	  wanted_type_ptr->wanted_type = wanted_type;
+	  wanted_type_ptr->wanted_type_name = wanted_type_name;
+	  wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
+	  wanted_type_ptr->char_lenient_flag = 0;
+	  if (strchr (fci->flags2, 'c') != 0)
+	    wanted_type_ptr->char_lenient_flag = 1;
+	  wanted_type_ptr->scalar_identity_flag = 0;
+	  if (len_modifier.scalar_identity_flag)
+	    wanted_type_ptr->scalar_identity_flag = 1;
+	  wanted_type_ptr->writing_in_flag = 0;
+	  wanted_type_ptr->reading_from_flag = 0;
+	  if (alloc_flag)
+	    wanted_type_ptr->writing_in_flag = 1;
+	  else
 	    {
-	      if (ADJ_STD (wanted_type_std) > C_STD_VER)
-		warning_at (location_from_offset (format_string_loc,
-						  format_chars - orig_format_chars),
-			    OPT_Wformat_,
-			    "%s does not support the %<%%%s%c%> %s format",
-			    C_STD_NAME (wanted_type_std), length_chars,
-			    format_char, fki->name);
+	      if (strchr (fci->flags2, 'W') != 0)
+		wanted_type_ptr->writing_in_flag = 1;
+	      if (strchr (fci->flags2, 'R') != 0)
+		wanted_type_ptr->reading_from_flag = 1;
+	    }
+	  wanted_type_ptr->kind = CF_KIND_FORMAT;
+	  wanted_type_ptr->param = cur_param;
+	  wanted_type_ptr->arg_num = arg_num;
+	  wanted_type_ptr->format_start = format_start;
+	  wanted_type_ptr->format_length = format_chars - format_start;
+	  wanted_type_ptr->offset_loc = format_chars - orig_format_chars;
+	  wanted_type_ptr->next = NULL;
+	  if (last_wanted_type != 0)
+	    last_wanted_type->next = wanted_type_ptr;
+	  if (first_wanted_type == 0)
+	    first_wanted_type = wanted_type_ptr;
+	  last_wanted_type = wanted_type_ptr;
+
+	  fci = fci->chain;
+	  if (fci)
+	    {
+	      wanted_type_ptr = fwt_pool.allocate ();
+	      arg_num++;
+	      wanted_type = *fci->types[len_modifier.val].type;
+	      wanted_type_name = fci->types[len_modifier.val].name;
 	    }
 	}
+    }
 
-      main_wanted_type.next = NULL;
+  if (first_wanted_type != 0)
+    check_format_types (format_string_loc, first_wanted_type);
 
-      /* Finally. . .check type of argument against desired type!  */
-      if (info->first_arg_num == 0)
+  return true;
+}
+
+/* Do the main part of checking a call to a format function.  FORMAT_CHARS
+   is the NUL-terminated format string (which at this point may contain
+   internal NUL characters); FORMAT_LENGTH is its length (excluding the
+   terminating NUL character).  ARG_NUM is one less than the number of
+   the first format argument to check; PARAMS points to that format
+   argument in the list of arguments.  */
+
+static void
+check_format_info_main (format_check_results *res,
+			function_format_info *info, const char *format_chars,
+			int format_length, tree params,
+			unsigned HOST_WIDE_INT arg_num,
+			object_allocator <format_wanted_type> &fwt_pool)
+{
+  const char * const orig_format_chars = format_chars;
+  const tree first_fillin_param = params;
+
+  const format_kind_info * const fki = &format_types[info->format_type];
+  const format_flag_spec * const flag_specs = fki->flag_specs;
+  const location_t format_string_loc = res->format_string_loc;
+
+  /* -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.  */
+  int has_operand_number = -1;
+
+  init_dollar_format_checking (info->first_arg_num, first_fillin_param);
+
+  while (*format_chars != 0)
+    {
+      if (*format_chars++ != '%')
 	continue;
-      if ((fci->pointer_count == 0 && wanted_type == void_type_node)
-	  || suppressed)
+      if (*format_chars == 0)
 	{
-	  if (main_arg_num != 0)
-	    {
-	      if (suppressed)
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "operand number specified with "
-			    "suppressed assignment");
-	      else
-		warning_at (format_string_loc, OPT_Wformat_,
-			    "operand number specified for format "
-			    "taking no argument");
-	    }
+          warning_at (location_from_offset (format_string_loc,
+					    format_chars - orig_format_chars),
+		      OPT_Wformat_,
+		      "spurious trailing %<%%%> in format");
+	  continue;
 	}
-      else
+      if (*format_chars == '%')
 	{
-	  format_wanted_type *wanted_type_ptr;
+	  ++format_chars;
+	  continue;
+	}
 
-	  if (main_arg_num != 0)
-	    {
-	      arg_num = main_arg_num;
-	      params = main_arg_params;
-	    }
-	  else
-	    {
-	      ++arg_num;
-	      if (has_operand_number > 0)
-		{
-		  warning_at (format_string_loc, OPT_Wformat_,
-			      "missing $ operand number in format");
-		  return;
-		}
-	      else
-		has_operand_number = 0;
-	    }
+      flag_chars_t flag_chars;
+      argument_parser arg_parser (info, format_chars, orig_format_chars,
+				  format_string_loc,
+				  flag_chars, has_operand_number,
+				  first_fillin_param, fwt_pool);
 
-	  wanted_type_ptr = &main_wanted_type;
-	  while (fci)
-	    {
-	      if (params == 0)
-                cur_param = NULL;
-              else
-                {
-                  cur_param = TREE_VALUE (params);
-                  params = TREE_CHAIN (params);
-                }
-
-	      wanted_type_ptr->wanted_type = wanted_type;
-	      wanted_type_ptr->wanted_type_name = wanted_type_name;
-	      wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
-	      wanted_type_ptr->char_lenient_flag = 0;
-	      if (strchr (fci->flags2, 'c') != 0)
-		wanted_type_ptr->char_lenient_flag = 1;
-	      wanted_type_ptr->scalar_identity_flag = 0;
-	      if (scalar_identity_flag)
-		wanted_type_ptr->scalar_identity_flag = 1;
-	      wanted_type_ptr->writing_in_flag = 0;
-	      wanted_type_ptr->reading_from_flag = 0;
-	      if (alloc_flag)
-		wanted_type_ptr->writing_in_flag = 1;
-	      else
-		{
-		  if (strchr (fci->flags2, 'W') != 0)
-		    wanted_type_ptr->writing_in_flag = 1;
-		  if (strchr (fci->flags2, 'R') != 0)
-		    wanted_type_ptr->reading_from_flag = 1;
-		}
-              wanted_type_ptr->kind = CF_KIND_FORMAT;
-	      wanted_type_ptr->param = cur_param;
-	      wanted_type_ptr->arg_num = arg_num;
-	      wanted_type_ptr->format_start = format_start;
-	      wanted_type_ptr->format_length = format_chars - format_start;
-	      wanted_type_ptr->offset_loc = format_chars - orig_format_chars;
-	      wanted_type_ptr->next = NULL;
-	      if (last_wanted_type != 0)
-		last_wanted_type->next = wanted_type_ptr;
-	      if (first_wanted_type == 0)
-		first_wanted_type = wanted_type_ptr;
-	      last_wanted_type = wanted_type_ptr;
-
-	      fci = fci->chain;
-	      if (fci)
-		{
-		  wanted_type_ptr = fwt_pool.allocate ();
-		  arg_num++;
-		  wanted_type = *fci->types[length_chars_val].type;
-		  wanted_type_name = fci->types[length_chars_val].name;
-		}
-	    }
+      if (!arg_parser.read_any_dollar ())
+	return;
+
+      if (!arg_parser.read_format_flags ())
+	return;
+
+      /* Read any format width, possibly * or *m$.  */
+      if (!arg_parser.read_any_format_width (params, arg_num))
+	return;
+
+      /* Read any format left precision (must be a number, not *).  */
+      arg_parser.read_any_format_left_precision ();
+
+      /* Read any format precision, possibly * or *m$.  */
+      if (!arg_parser.read_any_format_precision (params, arg_num))
+	return;
+
+      const char *format_start = format_chars;
+
+      arg_parser.handle_alloc_chars ();
+
+      /* Read any length modifier, if this kind of format has them.  */
+      const length_modifier len_modifier
+	= arg_parser.read_any_length_modifier ();
+
+      /* Read any modifier (strftime E/O).  */
+      arg_parser.read_any_other_modifier ();
+
+      char format_char = *format_chars;
+      if (format_char == 0
+	  || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
+	      && format_char == '%'))
+	{
+	  warning_at (location_from_offset (format_string_loc,
+					    format_chars - orig_format_chars),
+		      OPT_Wformat_,
+		      "conversion lacks type at end of format");
+	  continue;
 	}
+      format_chars++;
 
-      if (first_wanted_type != 0)
-        check_format_types (format_string_loc, first_wanted_type);
+      const format_char_info * const fci
+	= arg_parser.find_format_char_info (format_char);
+      if (!fci)
+	continue;
+
+      flag_chars.validate (fki, fci, flag_specs, format_chars,
+			   format_string_loc, orig_format_chars, format_char);
+
+      const int alloc_flag = flag_chars.get_alloc_flag (fki);
+      const bool suppressed = flag_chars.assignment_suppression_p (fki);
+
+      /* Validate the pairs of flags used.  */
+      arg_parser.validate_flag_pairs (fci, format_char);
+
+      arg_parser.give_y2k_warnings (fci, format_char);
+
+      arg_parser.parse_any_scan_set (fci);
+
+      tree wanted_type = NULL;
+      const char *wanted_type_name = NULL;
+
+      if (!arg_parser.handle_conversions (fci, len_modifier,
+					  wanted_type, wanted_type_name,
+					  arg_num,
+					  params,
+					  format_char))
+	continue;
+
+      arg_parser.main_wanted_type.next = NULL;
+
+      /* Finally. . .check type of argument against desired type!  */
+      if (!arg_parser.check_argument_type (fci, len_modifier,
+					   wanted_type, wanted_type_name,
+					   suppressed,
+					   arg_num, params,
+					   alloc_flag,
+					   format_start))
+	return;
     }
 
   if (format_chars - orig_format_chars != format_length)
-- 
1.8.5.3


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