OSDN Git Service

* doc/fragments.texi, doc/trouble.texi: Remove links to old
[pf3gnuchains/gcc-fork.git] / gcc / c-format.c
index 331cd25..b224a89 100644 (file)
@@ -77,10 +77,16 @@ enum format_type { printf_format_type, scanf_format_type,
                   strftime_format_type, strfmon_format_type,
                   format_type_error };
 
+typedef struct function_format_info
+{
+  enum format_type format_type;        /* type of format (printf, scanf, etc.) */
+  unsigned HOST_WIDE_INT format_num;   /* number of format argument */
+  unsigned HOST_WIDE_INT first_arg_num;        /* number of first arg (zero for varargs) */
+} function_format_info;
+
+static bool decode_format_attr         PARAMS ((tree,
+                                                function_format_info *, int));
 static enum format_type decode_format_type     PARAMS ((const char *));
-static void record_function_format     PARAMS ((tree, tree, enum format_type,
-                                                int, int));
-static void record_international_format        PARAMS ((tree, tree, int));
 
 /* Handle a "format" attribute; arguments as in
    struct attribute_spec.handler.  */
@@ -89,75 +95,16 @@ handle_format_attribute (node, name, args, flags, no_add_attrs)
      tree *node;
      tree name ATTRIBUTE_UNUSED;
      tree args;
-     int flags ATTRIBUTE_UNUSED;
+     int flags;
      bool *no_add_attrs;
 {
-  tree decl = *node;
-  tree type = TREE_TYPE (decl);
-  tree format_type_id = TREE_VALUE (args);
-  tree format_num_expr = TREE_VALUE (TREE_CHAIN (args));
-  tree first_arg_num_expr
-    = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
-  unsigned HOST_WIDE_INT format_num, first_arg_num;
-  enum format_type format_type;
+  tree type = *node;
+  function_format_info info;
   tree argument;
-  unsigned int arg_num;
+  unsigned HOST_WIDE_INT arg_num;
 
-  if (TREE_CODE (decl) != FUNCTION_DECL)
+  if (!decode_format_attr (args, &info, 0))
     {
-      error_with_decl (decl,
-                      "argument format specified for non-function `%s'");
-      *no_add_attrs = true;
-      return NULL_TREE;
-    }
-
-  if (TREE_CODE (format_type_id) != IDENTIFIER_NODE)
-    {
-      error ("unrecognized format specifier");
-      *no_add_attrs = true;
-      return NULL_TREE;
-    }
-  else
-    {
-      const char *p = IDENTIFIER_POINTER (format_type_id);
-
-      format_type = decode_format_type (p);
-
-      if (format_type == format_type_error)
-       {
-         warning ("`%s' is an unrecognized format function type", p);
-         *no_add_attrs = true;
-         return NULL_TREE;
-       }
-    }
-
-  /* Strip any conversions from the string index and first arg number
-     and verify they are constants.  */
-  while (TREE_CODE (format_num_expr) == NOP_EXPR
-        || TREE_CODE (format_num_expr) == CONVERT_EXPR
-        || TREE_CODE (format_num_expr) == NON_LVALUE_EXPR)
-    format_num_expr = TREE_OPERAND (format_num_expr, 0);
-
-  while (TREE_CODE (first_arg_num_expr) == NOP_EXPR
-        || TREE_CODE (first_arg_num_expr) == CONVERT_EXPR
-        || TREE_CODE (first_arg_num_expr) == NON_LVALUE_EXPR)
-    first_arg_num_expr = TREE_OPERAND (first_arg_num_expr, 0);
-
-  if (TREE_CODE (format_num_expr) != INTEGER_CST
-      || TREE_INT_CST_HIGH (format_num_expr) != 0
-      || TREE_CODE (first_arg_num_expr) != INTEGER_CST
-      || TREE_INT_CST_HIGH (first_arg_num_expr) != 0)
-    {
-      error ("format string has invalid operand number");
-      *no_add_attrs = true;
-      return NULL_TREE;
-    }
-
-  format_num = TREE_INT_CST_LOW (format_num_expr);
-  first_arg_num = TREE_INT_CST_LOW (first_arg_num_expr);
-  if (first_arg_num != 0 && first_arg_num <= format_num)
-    {
-      error ("format string arg follows the args to be formatted");
       *no_add_attrs = true;
       return NULL_TREE;
     }
@@ -168,7 +115,7 @@ handle_format_attribute (node, name, args, flags, no_add_attrs)
   argument = TYPE_ARG_TYPES (type);
   if (argument)
     {
-      for (arg_num = 1; argument != 0 && arg_num != format_num;
+      for (arg_num = 1; argument != 0 && arg_num != info.format_num;
           ++arg_num, argument = TREE_CHAIN (argument))
        ;
 
@@ -177,65 +124,56 @@ handle_format_attribute (node, name, args, flags, no_add_attrs)
          || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument)))
              != char_type_node))
        {
-         error ("format string arg not a string type");
+         if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+           error ("format string arg not a string type");
          *no_add_attrs = true;
          return NULL_TREE;
        }
 
-      else if (first_arg_num != 0)
+      else if (info.first_arg_num != 0)
        {
          /* Verify that first_arg_num points to the last arg,
             the ...  */
          while (argument)
            arg_num++, argument = TREE_CHAIN (argument);
 
-         if (arg_num != first_arg_num)
+         if (arg_num != info.first_arg_num)
            {
-             error ("args to be formatted is not '...'");
+             if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+               error ("args to be formatted is not '...'");
              *no_add_attrs = true;
              return NULL_TREE;
            }
        }
     }
 
-  if (format_type == strftime_format_type && first_arg_num != 0)
+  if (info.format_type == strftime_format_type && info.first_arg_num != 0)
     {
       error ("strftime formats cannot format arguments");
       *no_add_attrs = true;
       return NULL_TREE;
     }
 
-  record_function_format (DECL_NAME (decl), DECL_ASSEMBLER_NAME (decl),
-                         format_type, format_num, first_arg_num);
   return NULL_TREE;
 }
 
 
-/* Handle a "format" attribute; arguments as in
+/* Handle a "format_arg" attribute; arguments as in
    struct attribute_spec.handler.  */
 tree
 handle_format_arg_attribute (node, name, args, flags, no_add_attrs)
      tree *node;
      tree name ATTRIBUTE_UNUSED;
      tree args;
-     int flags ATTRIBUTE_UNUSED;
+     int flags;
      bool *no_add_attrs;
 {
-  tree decl = *node;
-  tree type = TREE_TYPE (decl);
+  tree type = *node;
   tree format_num_expr = TREE_VALUE (args);
   unsigned HOST_WIDE_INT format_num;
-  unsigned int arg_num;
+  unsigned HOST_WIDE_INT arg_num;
   tree argument;
 
-  if (TREE_CODE (decl) != FUNCTION_DECL)
-    {
-      error_with_decl (decl,
-                      "argument format specified for non-function `%s'");
-      *no_add_attrs = true;
-      return NULL_TREE;
-    }
-
   /* Strip any conversions from the first arg number and verify it
      is a constant.  */
   while (TREE_CODE (format_num_expr) == NOP_EXPR
@@ -268,200 +206,102 @@ handle_format_arg_attribute (node, name, args, flags, no_add_attrs)
          || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument)))
              != char_type_node))
        {
-         error ("format string arg not a string type");
+         if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+           error ("format string arg not a string type");
          *no_add_attrs = true;
          return NULL_TREE;
        }
     }
 
-  if (TREE_CODE (TREE_TYPE (TREE_TYPE (decl))) != POINTER_TYPE
-      || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (TREE_TYPE (decl))))
+  if (TREE_CODE (TREE_TYPE (type)) != POINTER_TYPE
+      || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (type)))
          != char_type_node))
     {
-      error ("function does not return string type");
+      if (!(flags & (int) ATTR_FLAG_BUILT_IN))
+       error ("function does not return string type");
       *no_add_attrs = true;
       return NULL_TREE;
     }
 
-  record_international_format (DECL_NAME (decl), DECL_ASSEMBLER_NAME (decl),
-                              format_num);
   return NULL_TREE;
 }
 
-typedef struct function_format_info
-{
-  struct function_format_info *next;  /* next structure on the list */
-  tree name;                   /* identifier such as "printf" */
-  tree assembler_name;         /* optional mangled identifier (for C++) */
-  enum format_type format_type;        /* type of format (printf, scanf, etc.) */
-  int format_num;              /* number of format argument */
-  int first_arg_num;           /* number of first arg (zero for varargs) */
-} function_format_info;
 
-static function_format_info *function_format_list = NULL;
+/* Decode the arguments to a "format" attribute into a function_format_info
+   structure.  It is already known that the list is of the right length.
+   If VALIDATED_P is true, then these attributes have already been validated
+   and this function will abort if they are erroneous; if false, it
+   will give an error message.  Returns true if the attributes are
+   successfully decoded, false otherwise.  */
 
-typedef struct international_format_info
-{
-  struct international_format_info *next;  /* next structure on the list */
-  tree name;                   /* identifier such as "gettext" */
-  tree assembler_name;         /* optional mangled identifier (for C++) */
-  int format_num;              /* number of format argument */
-} international_format_info;
-
-static international_format_info *international_format_list = NULL;
-
-/* Initialize the table of functions to perform format checking on.
-   The ISO C functions are always checked (whether <stdio.h> is
-   included or not), since it is common to call printf without
-   including <stdio.h>.  There shouldn't be a problem with this,
-   since ISO C reserves these function names whether you include the
-   header file or not.  In any case, the checking is harmless.  With
-   -ffreestanding, these default attributes are disabled, and must be
-   specified manually if desired.
-
-   Also initialize the name of function that modify the format string for
-   internationalization purposes.  */
-
-void
-init_function_format_info ()
+static bool
+decode_format_attr (args, info, validated_p)
+     tree args;
+     function_format_info *info;
+     int validated_p;
 {
-  if (flag_hosted)
-    {
-      /* Functions from ISO/IEC 9899:1990.  */
-      record_function_format (get_identifier ("printf"), NULL_TREE,
-                             printf_format_type, 1, 2);
-      record_function_format (get_identifier ("__builtin_printf"), NULL_TREE,
-                             printf_format_type, 1, 2);
-      record_function_format (get_identifier ("fprintf"), NULL_TREE,
-                             printf_format_type, 2, 3);
-      record_function_format (get_identifier ("__builtin_fprintf"), NULL_TREE,
-                             printf_format_type, 2, 3);
-      record_function_format (get_identifier ("sprintf"), NULL_TREE,
-                             printf_format_type, 2, 3);
-      record_function_format (get_identifier ("scanf"), NULL_TREE,
-                             scanf_format_type, 1, 2);
-      record_function_format (get_identifier ("fscanf"), NULL_TREE,
-                             scanf_format_type, 2, 3);
-      record_function_format (get_identifier ("sscanf"), NULL_TREE,
-                             scanf_format_type, 2, 3);
-      record_function_format (get_identifier ("vprintf"), NULL_TREE,
-                             printf_format_type, 1, 0);
-      record_function_format (get_identifier ("vfprintf"), NULL_TREE,
-                             printf_format_type, 2, 0);
-      record_function_format (get_identifier ("vsprintf"), NULL_TREE,
-                             printf_format_type, 2, 0);
-      record_function_format (get_identifier ("strftime"), NULL_TREE,
-                             strftime_format_type, 3, 0);
-    }
+  tree format_type_id = TREE_VALUE (args);
+  tree format_num_expr = TREE_VALUE (TREE_CHAIN (args));
+  tree first_arg_num_expr
+    = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
 
-  if (flag_hosted && flag_isoc99)
+  if (TREE_CODE (format_type_id) != IDENTIFIER_NODE)
     {
-      /* ISO C99 adds the snprintf and vscanf family functions.  */
-      record_function_format (get_identifier ("snprintf"), NULL_TREE,
-                             printf_format_type, 3, 4);
-      record_function_format (get_identifier ("vsnprintf"), NULL_TREE,
-                             printf_format_type, 3, 0);
-      record_function_format (get_identifier ("vscanf"), NULL_TREE,
-                             scanf_format_type, 1, 0);
-      record_function_format (get_identifier ("vfscanf"), NULL_TREE,
-                             scanf_format_type, 2, 0);
-      record_function_format (get_identifier ("vsscanf"), NULL_TREE,
-                             scanf_format_type, 2, 0);
+      if (validated_p)
+       abort ();
+      error ("unrecognized format specifier");
+      return false;
     }
-
-  if (flag_hosted && flag_noniso_default_format_attributes)
+  else
     {
-      /* Uniforum/GNU gettext functions, not in ISO C.  */
-      record_international_format (get_identifier ("gettext"), NULL_TREE, 1);
-      record_international_format (get_identifier ("dgettext"), NULL_TREE, 2);
-      record_international_format (get_identifier ("dcgettext"), NULL_TREE, 2);
-      /* X/Open strfmon function.  */
-      record_function_format (get_identifier ("strfmon"), NULL_TREE,
-                             strfmon_format_type, 3, 4);
-    }
-}
-
-/* Record information for argument format checking.  FUNCTION_IDENT is
-   the identifier node for the name of the function to check (its decl
-   need not exist yet).
-   FORMAT_TYPE specifies the type of format checking.  FORMAT_NUM is the number
-   of the argument which is the format control string (starting from 1).
-   FIRST_ARG_NUM is the number of the first actual argument to check
-   against the format string, or zero if no checking is not be done
-   (e.g. for varargs such as vfprintf).  */
-
-static void
-record_function_format (name, assembler_name, format_type,
-                       format_num, first_arg_num)
-      tree name;
-      tree assembler_name;
-      enum format_type format_type;
-      int format_num;
-      int first_arg_num;
-{
-  function_format_info *info;
+      const char *p = IDENTIFIER_POINTER (format_type_id);
 
-  /* Re-use existing structure if it's there.  */
+      info->format_type = decode_format_type (p);
 
-  for (info = function_format_list; info; info = info->next)
-    {
-      if (info->name == name && info->assembler_name == assembler_name)
-       break;
-    }
-  if (! info)
-    {
-      info = (function_format_info *) xmalloc (sizeof (function_format_info));
-      info->next = function_format_list;
-      function_format_list = info;
-
-      info->name = name;
-      info->assembler_name = assembler_name;
+      if (info->format_type == format_type_error)
+       {
+         if (validated_p)
+           abort ();
+         warning ("`%s' is an unrecognized format function type", p);
+         return false;
+       }
     }
 
-  info->format_type = format_type;
-  info->format_num = format_num;
-  info->first_arg_num = first_arg_num;
-}
-
-/* Record information for the names of function that modify the format
-   argument to format functions.  FUNCTION_IDENT is the identifier node for
-   the name of the function (its decl need not exist yet) and FORMAT_NUM is
-   the number of the argument which is the format control string (starting
-   from 1).  */
-
-static void
-record_international_format (name, assembler_name, format_num)
-      tree name;
-      tree assembler_name;
-      int format_num;
-{
-  international_format_info *info;
+  /* Strip any conversions from the string index and first arg number
+     and verify they are constants.  */
+  while (TREE_CODE (format_num_expr) == NOP_EXPR
+        || TREE_CODE (format_num_expr) == CONVERT_EXPR
+        || TREE_CODE (format_num_expr) == NON_LVALUE_EXPR)
+    format_num_expr = TREE_OPERAND (format_num_expr, 0);
 
-  /* Re-use existing structure if it's there.  */
+  while (TREE_CODE (first_arg_num_expr) == NOP_EXPR
+        || TREE_CODE (first_arg_num_expr) == CONVERT_EXPR
+        || TREE_CODE (first_arg_num_expr) == NON_LVALUE_EXPR)
+    first_arg_num_expr = TREE_OPERAND (first_arg_num_expr, 0);
 
-  for (info = international_format_list; info; info = info->next)
+  if (TREE_CODE (format_num_expr) != INTEGER_CST
+      || TREE_INT_CST_HIGH (format_num_expr) != 0
+      || TREE_CODE (first_arg_num_expr) != INTEGER_CST
+      || TREE_INT_CST_HIGH (first_arg_num_expr) != 0)
     {
-      if (info->name == name && info->assembler_name == assembler_name)
-       break;
+      if (validated_p)
+       abort ();
+      error ("format string has invalid operand number");
+      return false;
     }
 
-  if (! info)
+  info->format_num = TREE_INT_CST_LOW (format_num_expr);
+  info->first_arg_num = TREE_INT_CST_LOW (first_arg_num_expr);
+  if (info->first_arg_num != 0 && info->first_arg_num <= info->format_num)
     {
-      info
-       = (international_format_info *)
-         xmalloc (sizeof (international_format_info));
-      info->next = international_format_list;
-      international_format_list = info;
-
-      info->name = name;
-      info->assembler_name = assembler_name;
+      if (validated_p)
+       abort ();
+      error ("format string arg follows the args to be formatted");
+      return false;
     }
 
-  info->format_num = format_num;
+  return true;
 }
-
-
-
 \f
 /* Check a call to a format function against a parameter list.  */
 
@@ -494,7 +334,7 @@ enum format_std_version
 
 /* The C standard version C++ is treated as equivalent to
    or inheriting from, for the purpose of format features supported.  */
-#define CPLUSPLUS_STD_VER      STD_C89
+#define CPLUSPLUS_STD_VER      STD_C94
 /* The C standard version we are checking formats against when pedantic.  */
 #define C_STD_VER              ((int)(c_language == clk_cplusplus        \
                                 ? CPLUSPLUS_STD_VER                      \
@@ -547,19 +387,19 @@ enum
 typedef struct
 {
   /* Name of the single-character length modifier.  */
-  const char *name;
+  const char *const name;
   /* Index into a format_char_info.types array.  */
-  enum format_lengths index;
+  const enum format_lengths index;
   /* Standard version this length appears in.  */
-  enum format_std_version std;
+  const enum format_std_version std;
   /* Same, if the modifier can be repeated, or NULL if it can't.  */
-  const char *double_name;
-  enum format_lengths double_index;
-  enum format_std_version double_std;
+  const char *const double_name;
+  const enum format_lengths double_index;
+  const enum format_std_version double_std;
 } format_length_info;
 
 
-/* Structure desribing the combination of a conversion specifier
+/* Structure describing the combination of a conversion specifier
    (or a set of specifiers which act identically) and a length modifier.  */
 typedef struct
 {
@@ -580,22 +420,22 @@ typedef struct
 #define NOLENGTHS      { BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
 
 
-/* Structure desribing a format conversion specifier (or a set of specifiers
+/* Structure describing a format conversion specifier (or a set of specifiers
    which act identically), and the length modifiers used with it.  */
 typedef struct
 {
-  const char *format_chars;
-  int pointer_count;
-  enum format_std_version std;
+  const char *const format_chars;
+  const int pointer_count;
+  const enum format_std_version std;
   /* Types accepted for each length modifier.  */
-  format_type_detail types[FMT_LEN_MAX];
+  const format_type_detail types[FMT_LEN_MAX];
   /* List of other modifier characters allowed with these specifiers.
      This lists flags, and additionally "w" for width, "p" for precision
      (right precision, for strfmon), "#" for left precision (strfmon),
      "a" for scanf "a" allocation extension (not applicable in C99 mode),
      "*" for scanf suppression, and "E" and "O" for those strftime
      modifiers.  */
-  const char *flag_chars;
+  const char *const flag_chars;
   /* List of additional flags describing these conversion specifiers.
      "c" for generic character pointers being allowed, "2" for strftime
      two digit year formats, "3" for strftime formats giving two digit
@@ -605,7 +445,7 @@ typedef struct
      "R" if the argument is a pointer which is dereferenced and read from,
      "i" for printf integer formats where the '0' flag is ignored with
      precision, and "[" for the starting character of a scanf scanset.  */
-  const char *flags2;
+  const char *const flags2;
 } format_char_info;
 
 
@@ -613,7 +453,7 @@ typedef struct
 typedef struct
 {
   /* The flag character in question (0 for end of array).  */
-  int flag_char;
+  const int flag_char;
   /* Zero if this entry describes the flag character in general, or a
      non-zero character that may be found in flags2 if it describes the
      flag when used with certain formats only.  If the latter, only
@@ -622,18 +462,18 @@ typedef struct
      will be used, if non-NULL and the standard version is higher than
      the unpredicated one, for any pedantic warning.  For example, 'o'
      for strftime formats (meaning 'O' is an extension over C99).  */
-  int predicate;
+  const int predicate;
   /* Nonzero if the next character after this flag in the format should
      be skipped ('=' in strfmon), zero otherwise.  */
-  int skip_next_char;
+  const int skip_next_char;
   /* The name to use for this flag in diagnostic messages.  For example,
      N_("`0' flag"), N_("field width").  */
-  const char *name;
+  const char *const name;
   /* Long name for this flag in diagnostic messages; currently only used for
      "ISO C does not support ...".  For example, N_("the `I' printf flag").  */
-  const char *long_name;
+  const char *const long_name;
   /* The standard version in which it appeared.  */
-  enum format_std_version std;
+  const enum format_std_version std;
 } format_flag_spec;
 
 
@@ -642,16 +482,16 @@ typedef struct
 typedef struct
 {
   /* The first flag character in question (0 for end of array).  */
-  int flag_char1;
+  const int flag_char1;
   /* The second flag character.  */
-  int flag_char2;
+  const int flag_char2;
   /* Non-zero if the message should say that the first flag is ignored with
      the second, zero if the combination should simply be objected to.  */
-  int ignored;
+  const int ignored;
   /* Zero if this entry applies whenever this flag combination occurs,
      a non-zero character from flags2 if it only applies in some
      circumstances (e.g. 'i' for printf formats ignoring 0 with precision).  */
-  int predicate;
+  const int predicate;
 } format_flag_pair;
 
 
@@ -660,43 +500,43 @@ typedef struct
 {
   /* The name of this kind of format, for use in diagnostics.  Also
      the name of the attribute (without preceding and following __).  */
-  const char *name;
+  const char *const name;
   /* Specifications of the length modifiers accepted; possibly NULL.  */
-  const format_length_info *length_char_specs;
+  const format_length_info *const length_char_specs;
   /* Details of the conversion specification characters accepted.  */
-  const format_char_info *conversion_specs;
+  const format_char_info *const conversion_specs;
   /* String listing the flag characters that are accepted.  */
-  const char *flag_chars;
+  const char *const flag_chars;
   /* String listing modifier characters (strftime) accepted.  May be NULL.  */
-  const char *modifier_chars;
+  const char *const modifier_chars;
   /* Details of the flag characters, including pseudo-flags.  */
-  const format_flag_spec *flag_specs;
+  const format_flag_spec *const flag_specs;
   /* Details of bad combinations of flags.  */
-  const format_flag_pair *bad_flag_pairs;
+  const format_flag_pair *const bad_flag_pairs;
   /* Flags applicable to this kind of format.  */
-  int flags;
+  const int flags;
   /* Flag character to treat a width as, or 0 if width not used.  */
-  int width_char;
+  const int width_char;
   /* Flag character to treat a left precision (strfmon) as,
      or 0 if left precision not used.  */
-  int left_precision_char;
+  const int left_precision_char;
   /* Flag character to treat a precision (for strfmon, right precision) as,
      or 0 if precision not used.  */
-  int precision_char;
+  const int precision_char;
   /* If a flag character has the effect of suppressing the conversion of
      an argument ('*' in scanf), that flag character, otherwise 0.  */
-  int suppression_char;
+  const int suppression_char;
   /* Flag character to treat a length modifier as (ignored if length
      modifiers not used).  Need not be placed in flag_chars for conversion
      specifiers, but is used to check for bad combinations such as length
      modifier with assignment suppression in scanf.  */
-  int length_code_char;
+  const int length_code_char;
   /* Pointer to type of argument expected if '*' is used for a width,
      or NULL if '*' not used for widths.  */
-  tree *width_type;
+  tree *const width_type;
   /* Pointer to type of argument expected if '*' is used for a precision,
      or NULL if '*' not used for precisions.  */
-  tree *precision_type;
+  tree *const precision_type;
 } format_kind_info;
 
 
@@ -799,12 +639,12 @@ static const format_flag_pair printf_flag_pairs[] =
 
 static const format_flag_spec scanf_flag_specs[] =
 {
-  { '*',  0, 0, N_("assignment suppression"), N_("assignment suppression"),          STD_C89 },
-  { 'a',  0, 0, N_("`a' flag"),               N_("the `a' scanf flag"),              STD_EXT },
-  { 'w',  0, 0, N_("field width"),            N_("field width in scanf format"),     STD_C89 },
-  { 'L',  0, 0, N_("length modifier"),        N_("length modifier in scanf format"), STD_C89 },
-  { '\'', 0, 0, N_("`'' flag"),               N_("the `'' scanf flag"),              STD_EXT },
-  { 'I',  0, 0, N_("`I' flag"),               N_("the `I' scanf flag"),              STD_EXT },
+  { '*',  0, 0, N_("assignment suppression"), N_("the assignment suppression scanf feature"), STD_C89 },
+  { 'a',  0, 0, N_("`a' flag"),               N_("the `a' scanf flag"),                       STD_EXT },
+  { 'w',  0, 0, N_("field width"),            N_("field width in scanf format"),              STD_C89 },
+  { 'L',  0, 0, N_("length modifier"),        N_("length modifier in scanf format"),          STD_C89 },
+  { '\'', 0, 0, N_("`'' flag"),               N_("the `'' scanf flag"),                       STD_EXT },
+  { 'I',  0, 0, N_("`I' flag"),               N_("the `I' scanf flag"),                       STD_EXT },
   { 0, 0, 0, NULL, NULL, 0 }
 };
 
@@ -1055,10 +895,11 @@ typedef struct
 static void check_format_info  PARAMS ((int *, function_format_info *, tree));
 static void check_format_info_recurse PARAMS ((int *, format_check_results *,
                                               function_format_info *, tree,
-                                              tree, int));
+                                              tree, unsigned HOST_WIDE_INT));
 static void check_format_info_main PARAMS ((int *, format_check_results *,
                                            function_format_info *,
-                                           const char *, int, tree, int));
+                                           const char *, int, tree,
+                                           unsigned HOST_WIDE_INT));
 static void status_warning PARAMS ((int *, const char *, ...))
      ATTRIBUTE_PRINTF_2;
 
@@ -1099,43 +940,42 @@ decode_format_type (s)
 
 \f
 /* Check the argument list of a call to printf, scanf, etc.
-   NAME is the function identifier.
-   ASSEMBLER_NAME is the function's assembler identifier.
-   (Either NAME or ASSEMBLER_NAME, but not both, may be NULL_TREE.)
+   ATTRS are the attributes on the function type.
    PARAMS is the list of argument values.  Also, if -Wmissing-format-attribute,
    warn for calls to vprintf or vscanf in functions with no such format
    attribute themselves.  */
 
 void
-check_function_format (status, name, assembler_name, params)
+check_function_format (status, attrs, params)
      int *status;
-     tree name;
-     tree assembler_name;
+     tree attrs;
      tree params;
 {
-  function_format_info *info;
+  tree a;
 
-  /* See if this function is a format function.  */
-  for (info = function_format_list; info; info = info->next)
+  /* See if this function has any format attributes.  */
+  for (a = attrs; a; a = TREE_CHAIN (a))
     {
-      if (info->assembler_name
-         ? (info->assembler_name == assembler_name)
-         : (info->name == name))
+      if (is_attribute_p ("format", TREE_PURPOSE (a)))
        {
          /* Yup; check it.  */
-         check_format_info (status, info, params);
-         if (warn_missing_format_attribute && info->first_arg_num == 0
-             && (format_types[info->format_type].flags
+         function_format_info info;
+         decode_format_attr (TREE_VALUE (a), &info, 1);
+         check_format_info (status, &info, params);
+         if (warn_missing_format_attribute && info.first_arg_num == 0
+             && (format_types[info.format_type].flags
                  & (int) FMT_FLAG_ARG_CONVERT))
            {
-             function_format_info *info2;
-             for (info2 = function_format_list; info2; info2 = info2->next)
-               if ((info2->assembler_name
-                    ? (info2->assembler_name == DECL_ASSEMBLER_NAME (current_function_decl))
-                    : (info2->name == DECL_NAME (current_function_decl)))
-                   && info2->format_type == info->format_type)
+             tree c;
+             for (c = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl));
+                  c;
+                  c = TREE_CHAIN (c))
+               if (is_attribute_p ("format", TREE_PURPOSE (c))
+                   && (decode_format_type (IDENTIFIER_POINTER
+                                           (TREE_VALUE (TREE_VALUE (c))))
+                       == info.format_type))
                  break;
-             if (info2 == NULL)
+             if (c == NULL_TREE)
                {
                  /* Check if the current function has a parameter to which
                     the format attribute could be attached; if not, it
@@ -1153,10 +993,9 @@ check_function_format (status, name, assembler_name, params)
                    }
                  if (args != 0)
                    warning ("function might be possible candidate for `%s' format attribute",
-                            format_types[info->format_type].name);
+                            format_types[info.format_type].name);
                }
            }
-         break;
        }
     }
 }
@@ -1253,7 +1092,7 @@ maybe_read_dollar_number (status, format, dollar_needed, params, param_ptr,
   int argnum;
   int overflow_flag;
   const char *fcp = *format;
-  if (*fcp < '0' || *fcp > '9')
+  if (! ISDIGIT (*fcp))
     {
       if (dollar_needed)
        {
@@ -1265,7 +1104,7 @@ maybe_read_dollar_number (status, format, dollar_needed, params, param_ptr,
     }
   argnum = 0;
   overflow_flag = 0;
-  while (*fcp >= '0' && *fcp <= '9')
+  while (ISDIGIT (*fcp))
     {
       int nargnum;
       nargnum = 10 * argnum + (*fcp - '0');
@@ -1414,7 +1253,7 @@ check_format_info (status, info, params)
      function_format_info *info;
      tree params;
 {
-  int arg_num;
+  unsigned HOST_WIDE_INT arg_num;
   tree format_tree;
   format_check_results res;
   /* Skip to format argument.  If the argument isn't available, there's
@@ -1509,7 +1348,7 @@ check_format_info_recurse (status, res, info, format_tree, params, arg_num)
      function_format_info *info;
      tree format_tree;
      tree params;
-     int arg_num;
+     unsigned HOST_WIDE_INT arg_num;
 {
   int format_length;
   HOST_WIDE_INT offset;
@@ -1526,40 +1365,58 @@ check_format_info_recurse (status, res, info, format_tree, params, arg_num)
       return;
     }
 
-  if (TREE_CODE (format_tree) == CALL_EXPR
-      && TREE_CODE (TREE_OPERAND (format_tree, 0)) == ADDR_EXPR
-      && (TREE_CODE (TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0))
-         == FUNCTION_DECL))
+  if (TREE_CODE (format_tree) == CALL_EXPR)
     {
-      tree function = TREE_OPERAND (TREE_OPERAND (format_tree, 0), 0);
+      tree type = TREE_TYPE (TREE_TYPE (TREE_OPERAND (format_tree, 0)));
+      tree attrs;
+      bool found_format_arg = false;
 
       /* See if this is a call to a known internationalization function
-        that modifies the format arg.  */
-      international_format_info *iinfo;
+        that modifies the format arg.  Such a function may have multiple
+        format_arg attributes (for example, ngettext).  */
 
-      for (iinfo = international_format_list; iinfo; iinfo = iinfo->next)
-       if (iinfo->assembler_name
-           ? (iinfo->assembler_name == DECL_ASSEMBLER_NAME (function))
-           : (iinfo->name == DECL_NAME (function)))
+      for (attrs = TYPE_ATTRIBUTES (type);
+          attrs;
+          attrs = TREE_CHAIN (attrs))
+       if (is_attribute_p ("format_arg", TREE_PURPOSE (attrs)))
          {
            tree inner_args;
+           tree format_num_expr;
+           int format_num;
            int i;
 
+           /* Extract the argument number, which was previously checked
+              to be valid.  */
+           format_num_expr = TREE_VALUE (TREE_VALUE (attrs));
+           while (TREE_CODE (format_num_expr) == NOP_EXPR
+                  || TREE_CODE (format_num_expr) == CONVERT_EXPR
+                  || TREE_CODE (format_num_expr) == NON_LVALUE_EXPR)
+             format_num_expr = TREE_OPERAND (format_num_expr, 0);
+
+           if (TREE_CODE (format_num_expr) != INTEGER_CST
+               || TREE_INT_CST_HIGH (format_num_expr) != 0)
+             abort ();
+
+           format_num = TREE_INT_CST_LOW (format_num_expr);
+
            for (inner_args = TREE_OPERAND (format_tree, 1), i = 1;
                 inner_args != 0;
                 inner_args = TREE_CHAIN (inner_args), i++)
-             if (i == iinfo->format_num)
+             if (i == format_num)
                {
-                 /* FIXME: with Marc Espie's __attribute__((nonnull))
-                    patch in GCC, we will have chained attributes,
-                    and be able to handle functions like ngettext
-                    with multiple format_arg attributes properly.  */
                  check_format_info_recurse (status, res, info,
                                             TREE_VALUE (inner_args), params,
                                             arg_num);
-                 return;
+                 found_format_arg = true;
+                 break;
                }
          }
+
+      /* If we found a format_arg attribute and did a recursive check,
+        we are done with checking this format string.  Otherwise, we
+        continue and this will count as a non-literal format string.  */
+      if (found_format_arg)
+       return;
     }
 
   if (TREE_CODE (format_tree) == COND_EXPR)
@@ -1733,7 +1590,7 @@ check_format_info_main (status, res, info, format_chars, format_length,
      const char *format_chars;
      int format_length;
      tree params;
-     int arg_num;
+     unsigned HOST_WIDE_INT arg_num;
 {
   const char *orig_format_chars = format_chars;
   tree first_fillin_param = params;
@@ -2505,8 +2362,8 @@ check_format_types (status, types)
        continue;
       /* Now we have a type mismatch.  */
       {
-       register const char *this;
-       register const char *that;
+       const char *this;
+       const char *that;
 
        this = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (wanted_type)));
        that = 0;
@@ -2528,9 +2385,9 @@ check_format_types (status, types)
        if (that == 0)
          {
            if (TREE_CODE (orig_cur_type) == POINTER_TYPE)
-             that = "pointer";
+             that = _("pointer");
            else
-             that = "different type";
+             that = _("different type");
          }
 
        /* Make the warning better in case of mismatch of int vs long.  */