c-format.c: cleanup of check_format_info_main
authorDavid Malcolm <dmalcolm@redhat.com>
Mon, 8 Aug 2016 18:10:54 +0000 (18:10 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Mon, 8 Aug 2016 18:10:54 +0000 (18:10 +0000)
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".

From-SVN: r239247

gcc/c-family/ChangeLog
gcc/c-family/c-format.c

index d5cdfed537a45175323d2c6c1d130d370ca07140..4374dbfb1b407b5ab5496dd06af41e1a17553a8b 100644 (file)
@@ -1,3 +1,33 @@
+2016-08-08  David Malcolm  <dmalcolm@redhat.com>
+
+       * 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".
+
 2016-08-05  David Malcolm  <dmalcolm@redhat.com>
 
        * c-common.c: Include "substring-locations.h".
index c19c411d61a44773c55c8686802787ec7e4c40a4..92d2c1cf5bea9895331ac6e39063545f19789582 100644 (file)
@@ -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)