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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Add __attribute__((format(wprintf, 2, 3))) etc. format checking support


Hi!

Following patch does 2 things:
1) adds wprintf/wscanf/wcsftime style format checking
2) ensures there are no format attribute duplicates added
   (the latter causes all format checking warnings doubled
    e.g. if glibc declares sprintf with format(printf, 2, 3) attribute
    but builtin-attrs.def added that attribute already first).
   This is needed because attribs.c only checks if attribute values
   are equal only if they are simple constants, which is not the
   case for format attributes.
I think not overloading printf/scanf/strftime formats is better than
overloading them, glibc has such attributes in its headers (commented out)
for ages. On the other side, using wformat doesn't look nicely either
(IMHO).
The only thing that is overloaded in the following patch is
format_arg attribute, which now handles both char * -> char * translator
functions and wchar_t * -> wchar_t * translators.
The code checks that the attribute is not used for char * -> wchar_t *
or wchar_t * -> char *.

Ok to commit?

2001-12-14  Jakub Jelinek  <jakub@redhat.com>

	* c-format.c (enum format_type): Add wprintf_format_type,
	wscanf_format_type, wcsftime_format_type.
	(function_format_info): Add char_size, wide, get_char.
	(GET_CHAR, ADVANCE_CHAR): Define.
	(handle_format_attribute): Check if format argument has proper
	type (wide char string, char string), depending on format type.
	Ensure wcsftime has third argument 0.
	Search for duplicates and avoid adding identical attributes.
	(handle_format_arg_attribute): Ensure the format argument is
	either char string or wide char string and the same type is
	returned.
	(decode_format_attr): Initialize wide, char_size and get_char
	info fields.
	(format_types): Add wprintf, wscanf and wcsftime.
	(format_check_results): Rename number_wide to number_wrong_type.
	(check_function_format): Check if the argument is either wide char
	or char string, depending on used format type.
	(maybe_read_dollar_number): Pass full function_format_info.
	Use GET_CHAR and ADVANCE_CHAR.
	(get_format_char): New.
	(get_format_wide_char): New.
	(check_format_info): If number_wrong_type > 0, complain depending
	on whether this is wide char or char format.
	(check_format_info_recurse): Ensure the argument matches format
	type (wide char or char).  Use GET_CHAR.
	(check_format_info_main): Use GET_CHAR and ADVANCE_CHAR.
	* builtin-attrs.def: Add wide character functions.
	* doc/extend.texi: Document wprintf, wscanf and wcsftime
	type formats.

	* gcc.dg/format/format.h: Add prototypes for wide character functions.
	* gcc.dg/format/c94-wprintf-1.c: New test.
	* gcc.dg/format/c94-wscanf-1.c: New test.
	* gcc.dg/format/c94-wcsftime-1.c: New test.
	* gcc.dg/format/wbranch-1.c: New test.

--- gcc/c-format.c.jj	Tue Nov 27 17:15:23 2001
+++ gcc/c-format.c	Fri Dec 14 18:36:38 2001
@@ -74,16 +74,24 @@ set_Wformat (setting)
 /* This must be in the same order as format_types, with format_type_error
    last.  */
 enum format_type { printf_format_type, scanf_format_type,
-		   strftime_format_type, strfmon_format_type,
-		   format_type_error };
+		   strftime_format_type, wprintf_format_type,
+		   wscanf_format_type, wcsftime_format_type,
+		   strfmon_format_type, format_type_error };
 
 typedef struct function_format_info
 {
   enum format_type format_type;	/* type of format (printf, scanf, etc.) */
+  int char_size;		/* sizeof(*format_string) */
+  int wide;			/* wprintf/wscanf/wcsftime */
+  /* Function to read one character from character literal.  */
+  int (*get_char) PARAMS ((const char *));
   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;
 
+#define GET_CHAR(offset) ((info->get_char) (format_chars + (offset)))
+#define ADVANCE_CHAR format_chars += info->char_size
+
 static bool decode_format_attr		PARAMS ((tree,
 						 function_format_info *, int));
 static enum format_type decode_format_type	PARAMS ((const char *));
@@ -100,7 +108,7 @@ handle_format_attribute (node, name, arg
 {
   tree type = *node;
   function_format_info info;
-  tree argument;
+  tree argument, old_attrs, a;
   unsigned HOST_WIDE_INT arg_num;
 
   if (!decode_format_attr (args, &info, 0))
@@ -122,7 +130,7 @@ handle_format_attribute (node, name, arg
       if (! argument
 	  || TREE_CODE (TREE_VALUE (argument)) != POINTER_TYPE
 	  || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument)))
-	      != char_type_node))
+	      != (info.wide ? wchar_type_node : char_type_node)))
 	{
 	  if (!(flags & (int) ATTR_FLAG_BUILT_IN))
 	    error ("format string arg not a string type");
@@ -154,6 +162,36 @@ handle_format_attribute (node, name, arg
       return NULL_TREE;
     }
 
+  if (info.format_type == wcsftime_format_type && info.first_arg_num != 0)
+    {
+      error ("wcsftime formats cannot format arguments");
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (DECL_P (*node))
+    old_attrs = DECL_ATTRIBUTES (*node);
+  else
+    old_attrs = TYPE_ATTRIBUTES (*node);
+
+  for (a = lookup_attribute ("format", old_attrs);
+       a != NULL_TREE;
+       a = lookup_attribute ("format", TREE_CHAIN (a)))
+    {
+      function_format_info old_info;
+      decode_format_attr (TREE_VALUE (a), &old_info, 1);
+      if (old_info.format_type == info.format_type
+	  && old_info.format_num == info.format_num
+	  && old_info.first_arg_num == info.first_arg_num)
+	{
+	  /* Don't add the same attribute twice.
+	     attribs.c duplicate check don't deal with TREE_LISTs
+	     format attribute is using.  */
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+    }
+
   return NULL_TREE;
 }
 
@@ -173,6 +211,7 @@ handle_format_arg_attribute (node, name,
   unsigned HOST_WIDE_INT format_num;
   unsigned HOST_WIDE_INT arg_num;
   tree argument;
+  tree format_type_node;
 
   /* Strip any conversions from the first arg number and verify it
      is a constant.  */
@@ -203,8 +242,10 @@ handle_format_arg_attribute (node, name,
 
       if (! argument
 	  || TREE_CODE (TREE_VALUE (argument)) != POINTER_TYPE
-	  || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument)))
-	      != char_type_node))
+	  || (((format_type_node
+		= TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument))))
+	       != char_type_node)
+	      && format_type_node != wchar_type_node))
 	{
 	  if (!(flags & (int) ATTR_FLAG_BUILT_IN))
 	    error ("format string arg not a string type");
@@ -215,7 +256,7 @@ handle_format_arg_attribute (node, name,
 
   if (TREE_CODE (TREE_TYPE (type)) != POINTER_TYPE
       || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (type)))
-	  != char_type_node))
+	  != format_type_node))
     {
       if (!(flags & (int) ATTR_FLAG_BUILT_IN))
 	error ("function does not return string type");
@@ -225,83 +266,6 @@ handle_format_arg_attribute (node, name,
 
   return NULL_TREE;
 }
-
-
-/* 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.  */
-
-static bool
-decode_format_attr (args, info, validated_p)
-     tree args;
-     function_format_info *info;
-     int validated_p;
-{
-  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 (TREE_CODE (format_type_id) != IDENTIFIER_NODE)
-    {
-      if (validated_p)
-	abort ();
-      error ("unrecognized format specifier");
-      return false;
-    }
-  else
-    {
-      const char *p = IDENTIFIER_POINTER (format_type_id);
-
-      info->format_type = decode_format_type (p);
-
-      if (info->format_type == format_type_error)
-	{
-	  if (validated_p)
-	    abort ();
-	  warning ("`%s' is an unrecognized format function type", p);
-	  return false;
-	}
-    }
-
-  /* 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)
-    {
-      if (validated_p)
-	abort ();
-      error ("format string has invalid operand number");
-      return false;
-    }
-
-  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)
-    {
-      if (validated_p)
-	abort ();
-      error ("format string arg follows the args to be formatted");
-      return false;
-    }
-
-  return true;
-}
 
 /* Check a call to a format function against a parameter list.  */
 
@@ -377,7 +341,9 @@ enum
   FMT_FLAG_EMPTY_PREC_OK = 64,
   /* Gaps are allowed in the arguments with $ operand numbers if all
      arguments are pointers (scanf).  */
-  FMT_FLAG_DOLLAR_GAP_POINTER_OK = 128
+  FMT_FLAG_DOLLAR_GAP_POINTER_OK = 128,
+  /* Wide string version.  */
+  FMT_FLAG_WIDE = 256
   /* Not included here: details of whether width or precision may occur
      (controlled by width_char and precision_char); details of whether
      '*' can be used for these (width_type and precision_type); details
@@ -860,6 +826,24 @@ static const format_kind_info format_typ
     FMT_FLAG_FANCY_PERCENT_OK, 'w', 0, 0, 0, 0,
     NULL, NULL
   },
+  { "wprintf",  printf_length_specs,  print_char_table, " +#0-'I", NULL, 
+    printf_flag_specs, printf_flag_pairs,
+    FMT_FLAG_ARG_CONVERT|FMT_FLAG_DOLLAR_MULTIPLE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_EMPTY_PREC_OK|FMT_FLAG_WIDE,
+    'w', 0, 'p', 0, 'L',
+    &integer_type_node, &integer_type_node
+  },
+  { "wscanf",   scanf_length_specs,   scan_char_table,  "*'I", NULL, 
+    scanf_flag_specs, scanf_flag_pairs,
+    FMT_FLAG_ARG_CONVERT|FMT_FLAG_SCANF_A_KLUDGE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_ZERO_WIDTH_BAD
+    |FMT_FLAG_DOLLAR_GAP_POINTER_OK|FMT_FLAG_WIDE,
+    'w', 0, 0, '*', 'L',
+    NULL, NULL
+  },
+  { "wcsftime", NULL,                 time_char_table,  "_-0^#", "EO",
+    strftime_flag_specs, strftime_flag_pairs,
+    FMT_FLAG_FANCY_PERCENT_OK|FMT_FLAG_WIDE, 'w', 0, 0, 0, 0,
+    NULL, NULL
+  },
   { "strfmon",  strfmon_length_specs, monetary_char_table, "=^+(!-", NULL, 
     strfmon_flag_specs, strfmon_flag_pairs,
     FMT_FLAG_ARG_CONVERT, 'w', '#', 'p', 0, 'L',
@@ -883,9 +867,9 @@ typedef struct
      string literals, but had extra format arguments and used $ operand
      numbers.  */
   int number_dollar_extra_args;
-  /* Number of leaves of the format argument that were wide string
-     literals.  */
-  int number_wide;
+  /* Number of leaves of the format argument that were neither normal
+     nor wide string literals.  */
+  int number_wrong_type;
   /* Number of leaves of the format argument that were empty strings.  */
   int number_empty;
   /* Number of leaves of the format argument that were unterminated
@@ -909,14 +893,104 @@ static void status_warning PARAMS ((int 
 static void init_dollar_format_checking		PARAMS ((int, tree));
 static int maybe_read_dollar_number		PARAMS ((int *, const char **, int,
 							 tree, tree *,
-							 const format_kind_info *));
+							 function_format_info *));
 static void finish_dollar_format_checking	PARAMS ((int *, format_check_results *, int));
 
 static const format_flag_spec *get_flag_spec	PARAMS ((const format_flag_spec *,
 							 int, const char *));
+static int get_format_char			PARAMS ((const char *));
+static int get_format_wide_char			PARAMS ((const char *));
 
 static void check_format_types	PARAMS ((int *, format_wanted_type *));
 
+/* 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.  */
+
+static bool
+decode_format_attr (args, info, validated_p)
+     tree args;
+     function_format_info *info;
+     int validated_p;
+{
+  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 (TREE_CODE (format_type_id) != IDENTIFIER_NODE)
+    {
+      if (validated_p)
+	abort ();
+      error ("unrecognized format specifier");
+      return false;
+    }
+  else
+    {
+      const char *p = IDENTIFIER_POINTER (format_type_id);
+
+      info->format_type = decode_format_type (p);
+
+      if (info->format_type == format_type_error)
+	{
+	  if (validated_p)
+	    abort ();
+	  warning ("`%s' is an unrecognized format function type", p);
+	  return false;
+	}
+    }
+
+  /* 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)
+    {
+      if (validated_p)
+	abort ();
+      error ("format string has invalid operand number");
+      return false;
+    }
+
+  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)
+    {
+      if (validated_p)
+	abort ();
+      error ("format string arg follows the args to be formatted");
+      return false;
+    }
+
+  info->wide = 0;
+  info->char_size = 1;
+  info->get_char = get_format_char;
+  if ((format_types[info->format_type].flags & (int) FMT_FLAG_WIDE))
+    {
+      info->wide = 1;
+      info->char_size = WCHAR_TYPE_SIZE / BITS_PER_UNIT;
+      if (info->char_size > 1)
+        info->get_char = get_format_wide_char;
+    }
+
+  return true;
+}
+
+
 /* Decode a format type from a string, returning the type, or
    format_type_error if not valid, in which case the caller should print an
    error message.  */
@@ -991,7 +1065,7 @@ check_function_format (status, attrs, pa
 		    {
 		      if (TREE_CODE (TREE_TYPE (args)) == POINTER_TYPE
 			  && (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (args)))
-			      == char_type_node))
+			      == (info.wide ? wchar_type_node : char_type_node)))
 			break;
 		    }
 		  if (args != 0)
@@ -1103,19 +1177,22 @@ init_dollar_format_checking (first_arg_n
    a $ format is found, *FORMAT is updated to point just after it.  */
 
 static int
-maybe_read_dollar_number (status, format, dollar_needed, params, param_ptr,
-			  fki)
+maybe_read_dollar_number (status, format, dollar_needed, params,
+			  param_ptr, info)
      int *status;
      const char **format;
      int dollar_needed;
      tree params;
      tree *param_ptr;
-     const format_kind_info *fki;
+     function_format_info *info;
 {
+  const format_kind_info *fki = &format_types[info->format_type];
   int argnum;
   int overflow_flag;
-  const char *fcp = *format;
-  if (! ISDIGIT (*fcp))
+  const char *format_chars = *format;
+  char c;
+
+  if (! ISDIGIT (GET_CHAR (0)))
     {
       if (dollar_needed)
 	{
@@ -1127,16 +1204,16 @@ maybe_read_dollar_number (status, format
     }
   argnum = 0;
   overflow_flag = 0;
-  while (ISDIGIT (*fcp))
+  while (ISDIGIT ((c = GET_CHAR (0))))
     {
       int nargnum;
-      nargnum = 10 * argnum + (*fcp - '0');
+      nargnum = 10 * argnum + (c - '0');
       if (nargnum < 0 || nargnum / 10 != argnum)
 	overflow_flag = 1;
       argnum = nargnum;
-      fcp++;
+      ADVANCE_CHAR;
     }
-  if (*fcp != '$')
+  if (GET_CHAR (0) != '$')
     {
       if (dollar_needed)
 	{
@@ -1146,7 +1223,8 @@ maybe_read_dollar_number (status, format
       else
 	return 0;
     }
-  *format = fcp + 1;
+  ADVANCE_CHAR;
+  *format = format_chars;
   if (pedantic && !dollar_format_warned)
     {
       status_warning (status,
@@ -1278,6 +1356,40 @@ get_flag_spec (spec, flag, predicates)
     return NULL;
 }
 
+/* Read one character from character literal.  */
+
+static int
+get_format_char (format_chars)
+     const char *format_chars;
+{
+  return *format_chars;
+}
+
+/* Read one character from wide character literal.
+   As all characters c-format is interested fit into char,
+   just return -1 if any of the upper bytes is non-zero.  */
+
+static int
+get_format_wide_char (format_chars)
+     const char *format_chars;
+{
+  unsigned int byte;
+
+  if (BYTES_BIG_ENDIAN)
+    {
+      for (byte = 0; byte < (WCHAR_TYPE_SIZE / BITS_PER_UNIT) - 1; byte++)
+	if (format_chars[byte])
+	  return -1;
+      return format_chars[(WCHAR_TYPE_SIZE / BITS_PER_UNIT) - 1];
+    }
+  else
+    {
+      for (byte = 1; byte < WCHAR_TYPE_SIZE / BITS_PER_UNIT; byte++)
+	if (format_chars[byte])
+	  return -1;
+      return format_chars[0];
+    }
+}
 
 /* Check the argument list of a call to printf, scanf, etc.
    INFO points to the function_format_info structure.
@@ -1310,7 +1422,7 @@ check_format_info (status, info, params)
   res.number_non_literal = 0;
   res.number_extra_args = 0;
   res.number_dollar_extra_args = 0;
-  res.number_wide = 0;
+  res.number_wrong_type = 0;
   res.number_empty = 0;
   res.number_unterminated = 0;
   res.number_other = 0;
@@ -1364,8 +1476,13 @@ check_format_info (status, info, params)
       && res.number_other == 0)
     status_warning (status, "zero-length format string");
 
-  if (res.number_wide > 0)
-    status_warning (status, "format is a wide character string");
+  if (res.number_wrong_type > 0)
+    {
+      if (info->wide)
+	status_warning (status, "format is not a wide character string");
+      else
+	status_warning (status, "format is a wide character string");
+    }
 
   if (res.number_unterminated > 0)
     status_warning (status, "unterminated format string");
@@ -1546,9 +1663,10 @@ check_format_info_recurse (status, res, 
       res->number_non_literal++;
       return;
     }
-  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree))) != char_type_node)
+  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree)))
+      != (info->wide ? wchar_type_node : char_type_node))
     {
-      res->number_wide++;
+      res->number_wrong_type++;
       return;
     }
   format_chars = TREE_STRING_POINTER (format_tree);
@@ -1577,17 +1695,19 @@ check_format_info_recurse (status, res, 
       format_chars += offset;
       format_length -= offset;
     }
-  if (format_length < 1)
+  if (format_length < info->char_size || format_length % info->char_size)
     {
       res->number_unterminated++;
       return;
     }
-  if (format_length == 1)
+  if (format_length == info->char_size)
     {
       res->number_empty++;
       return;
     }
-  if (format_chars[--format_length] != 0)
+
+  format_length -= info->char_size;
+  if (GET_CHAR (format_length) != 0)
     {
       res->number_unterminated++;
       return;
@@ -1629,6 +1749,7 @@ check_format_info_main (status, res, inf
      unsigned HOST_WIDE_INT arg_num;
 {
   const char *orig_format_chars = format_chars;
+  char c;
   tree first_fillin_param = params;
 
   const format_kind_info *fki = &format_types[info->format_type];
@@ -1664,7 +1785,8 @@ check_format_info_main (status, res, inf
       const format_char_info *fci = NULL;
       char flag_chars[256];
       int aflag = 0;
-      if (*format_chars == 0)
+      c = GET_CHAR (0);
+      if (c == '\0')
 	{
 	  if (format_chars - orig_format_chars != format_length)
 	    status_warning (status, "embedded `\\0' in format");
@@ -1678,16 +1800,18 @@ check_format_info_main (status, res, inf
 	    finish_dollar_format_checking (status, res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
 	  return;
 	}
-      if (*format_chars++ != '%')
+      ADVANCE_CHAR;
+      if (c != '%')
 	continue;
-      if (*format_chars == 0)
+      c = GET_CHAR (0);
+      if (c == 0)
 	{
 	  status_warning (status, "spurious trailing `%%' in format");
 	  continue;
 	}
-      if (*format_chars == '%')
+      if (c == '%')
 	{
-	  ++format_chars;
+	  ADVANCE_CHAR;
 	  continue;
 	}
       flag_chars[0] = 0;
@@ -1701,7 +1825,7 @@ check_format_info_main (status, res, inf
 	  int opnum;
 	  opnum = maybe_read_dollar_number (status, &format_chars, 0,
 					    first_fillin_param,
-					    &main_arg_params, fki);
+					    &main_arg_params, info);
 	  if (opnum == -1)
 	    return;
 	  else if (opnum > 0)
@@ -1714,44 +1838,42 @@ check_format_info_main (status, res, inf
       /* 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)
+      while ((c = GET_CHAR (0)) != 0 && strchr (fki->flag_chars, c) != 0)
 	{
-	  const format_flag_spec *s = get_flag_spec (flag_specs,
-						     *format_chars, NULL);
-	  if (strchr (flag_chars, *format_chars) != 0)
+	  const format_flag_spec *s = get_flag_spec (flag_specs, c, NULL);
+	  if (strchr (flag_chars, c) != 0)
 	    {
 	      status_warning (status, "repeated %s in format", _(s->name));
 	    }
 	  else
 	    {
 	      i = strlen (flag_chars);
-	      flag_chars[i++] = *format_chars;
+	      flag_chars[i++] = c;
 	      flag_chars[i] = 0;
 	    }
 	  if (s->skip_next_char)
 	    {
-	      ++format_chars;
-	      if (*format_chars == 0)
+	      ADVANCE_CHAR;
+	      if (GET_CHAR (0) == 0)
 		{
 		  status_warning (status, "missing fill character at end of strfmon format");
 		  return;
 		}
 	    }
-	  ++format_chars;
+	  ADVANCE_CHAR;
 	}
 
       /* Read any format width, possibly * or *m$.  */
       if (fki->width_char != 0)
 	{
-	  if (fki->width_type != NULL && *format_chars == '*')
+	  if (fki->width_type != NULL && c == '*')
 	    {
 	      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;
+	      ADVANCE_CHAR;
 	      if (params == 0)
 		{
 		  status_warning (status, "too few arguments for format");
@@ -1763,7 +1885,7 @@ check_format_info_main (status, res, inf
 		  opnum = maybe_read_dollar_number (status, &format_chars,
 						    has_operand_number == 1,
 						    first_fillin_param,
-						    &params, fki);
+						    &params, info);
 		  if (opnum == -1)
 		    return;
 		  else if (opnum > 0)
@@ -1805,12 +1927,12 @@ check_format_info_main (status, res, inf
 		 we complain if appropriate.  */
 	      int non_zero_width_char = FALSE;
 	      int found_width = FALSE;
-	      while (ISDIGIT (*format_chars))
+	      while (ISDIGIT ((c = GET_CHAR (0))))
 		{
 		  found_width = TRUE;
-		  if (*format_chars != '0')
+		  if (c != '0')
 		    non_zero_width_char = TRUE;
-		  ++format_chars;
+		  ADVANCE_CHAR;
 		}
 	      if (found_width && !non_zero_width_char &&
 		  (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
@@ -1826,38 +1948,38 @@ check_format_info_main (status, res, inf
 	}
 
       /* Read any format left precision (must be a number, not *).  */
-      if (fki->left_precision_char != 0 && *format_chars == '#')
+      if (fki->left_precision_char != 0 && GET_CHAR (0) == '#')
 	{
-	  ++format_chars;
+	  ADVANCE_CHAR;
 	  i = strlen (flag_chars);
 	  flag_chars[i++] = fki->left_precision_char;
 	  flag_chars[i] = 0;
-	  if (!ISDIGIT (*format_chars))
+	  if (!ISDIGIT (GET_CHAR (0)))
 	    status_warning (status, "empty left precision in %s format",
 			    fki->name);
-	  while (ISDIGIT (*format_chars))
-	    ++format_chars;
+	  while (ISDIGIT (GET_CHAR (0)))
+	    ADVANCE_CHAR;
 	}
 
       /* Read any format precision, possibly * or *m$.  */
-      if (fki->precision_char != 0 && *format_chars == '.')
+      if (fki->precision_char != 0 && GET_CHAR (0) == '.')
 	{
-	  ++format_chars;
+	  ADVANCE_CHAR;
 	  i = strlen (flag_chars);
 	  flag_chars[i++] = fki->precision_char;
 	  flag_chars[i] = 0;
-	  if (fki->precision_type != NULL && *format_chars == '*')
+	  if (fki->precision_type != NULL && GET_CHAR (0) == '*')
 	    {
 	      /* "...a...precision...may be indicated by an asterisk.
 		 In this case, an int argument supplies the...precision."  */
-	      ++format_chars;
+	      ADVANCE_CHAR;
 	      if (has_operand_number != 0)
 		{
 		  int opnum;
 		  opnum = maybe_read_dollar_number (status, &format_chars,
 						    has_operand_number == 1,
 						    first_fillin_param,
-						    &params, fki);
+						    &params, info);
 		  if (opnum == -1)
 		    return;
 		  else if (opnum > 0)
@@ -1901,11 +2023,11 @@ check_format_info_main (status, res, inf
 	  else
 	    {
 	      if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
-		  && !ISDIGIT (*format_chars))
+		  && !ISDIGIT (GET_CHAR (0)))
 		status_warning (status, "empty precision in %s format",
 				fki->name);
-	      while (ISDIGIT (*format_chars))
-		++format_chars;
+	      while (ISDIGIT (GET_CHAR (0)))
+		ADVANCE_CHAR;
 	    }
 	}
 
@@ -1916,14 +2038,15 @@ check_format_info_main (status, res, inf
       length_chars_std = STD_C89;
       if (fli)
 	{
-	  while (fli->name != 0 && fli->name[0] != *format_chars)
+	  c = GET_CHAR (0);
+	  while (fli->name != 0 && fli->name[0] != c)
 	    fli++;
 	  if (fli->name != 0)
 	    {
-	      format_chars++;
-	      if (fli->double_name != 0 && fli->name[0] == *format_chars)
+	      ADVANCE_CHAR;
+	      if (fli->double_name != 0 && fli->name[0] == GET_CHAR (0))
 		{
-		  format_chars++;
+		  ADVANCE_CHAR;
 		  length_chars = fli->double_name;
 		  length_chars_val = fli->double_index;
 		  length_chars_std = fli->double_std;
@@ -1951,43 +2074,43 @@ check_format_info_main (status, res, inf
       /* Read any modifier (strftime E/O).  */
       if (fki->modifier_chars != NULL)
 	{
-	  while (*format_chars != 0
-		 && strchr (fki->modifier_chars, *format_chars) != 0)
+	  while ((c = GET_CHAR (0)) != 0
+		 && strchr (fki->modifier_chars, c) != 0)
 	    {
-	      if (strchr (flag_chars, *format_chars) != 0)
+	      if (strchr (flag_chars, c) != 0)
 		{
-		  const format_flag_spec *s = get_flag_spec (flag_specs,
-							     *format_chars, NULL);
+		  const format_flag_spec *s = get_flag_spec (flag_specs, c,
+							     NULL);
 		  status_warning (status, "repeated %s in format", _(s->name));
 		}
 	      else
 		{
 		  i = strlen (flag_chars);
-		  flag_chars[i++] = *format_chars;
+		  flag_chars[i++] = c;
 		  flag_chars[i] = 0;
 		}
-	      ++format_chars;
+	      ADVANCE_CHAR;
 	    }
 	}
 
       /* Handle the scanf allocation kludge.  */
       if (fki->flags & (int) FMT_FLAG_SCANF_A_KLUDGE)
 	{
-	  if (*format_chars == 'a' && !flag_isoc99)
+	  if (GET_CHAR (0) == 'a' && !flag_isoc99)
 	    {
-	      if (format_chars[1] == 's' || format_chars[1] == 'S'
-		  || format_chars[1] == '[')
+	      c = GET_CHAR (1);
+	      if (c == 's' || c == 'S' || c == '[')
 		{
 		  /* `a' is used as a flag.  */
 		  i = strlen (flag_chars);
 		  flag_chars[i++] = 'a';
 		  flag_chars[i] = 0;
-		  format_chars++;
+		  ADVANCE_CHAR;
 		}
 	    }
 	}
 
-      format_char = *format_chars;
+      format_char = GET_CHAR (0);
       if (format_char == 0
 	  || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
 	      && format_char == '%'))
@@ -1995,7 +2118,7 @@ check_format_info_main (status, res, inf
 	  status_warning (status, "conversion lacks type at end of format");
 	  continue;
 	}
-      format_chars++;
+      ADVANCE_CHAR;
       fci = fki->conversion_specs;
       while (fci->format_chars != 0
 	     && strchr (fci->format_chars, format_char) == 0)
@@ -2122,15 +2245,15 @@ check_format_info_main (status, res, inf
       if (strchr (fci->flags2, '[') != 0)
 	{
 	  /* Skip over scan set, in case it happens to have '%' in it.  */
-	  if (*format_chars == '^')
-	    ++format_chars;
+	  if (GET_CHAR (0) == '^')
+	    ADVANCE_CHAR;
 	  /* 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 != ']')
+	  if (GET_CHAR (0) == ']')
+	    ADVANCE_CHAR;
+	  while ((c = GET_CHAR (0)) && c != ']')
+	    ADVANCE_CHAR;
+	  if (GET_CHAR (0) != ']')
 	    /* The end of the format string was reached.  */
 	    status_warning (status, "no closing `]' for `%%[' format");
 	}
--- gcc/doc/extend.texi.jj	Wed Dec 12 21:26:47 2001
+++ gcc/doc/extend.texi	Fri Dec 14 18:51:13 2001
@@ -1925,7 +1925,7 @@ the enclosing block.
 @cindex functions that behave like malloc
 @cindex @code{volatile} applied to function
 @cindex @code{const} applied to function
-@cindex functions with @code{printf}, @code{scanf}, @code{strftime} or @code{strfmon} style arguments
+@cindex functions with @code{printf}, @code{scanf}, @code{strftime}, @code{wprintf}, @code{wscanf}, @code{wcsftime} or @code{strfmon} style arguments
 @cindex functions that are passed arguments in registers on the 386
 @cindex functions that pop the argument stack on the 386
 @cindex functions that do not pop the argument stack on the 386
@@ -2061,7 +2061,8 @@ specifies that the @samp{const} must be 
 @cindex @code{format} function attribute
 @opindex Wformat
 The @code{format} attribute specifies that a function takes @code{printf},
-@code{scanf}, @code{strftime} or @code{strfmon} style arguments which
+@code{scanf}, @code{strftime}, @code{wprintf}, @code{wscanf},
+@code{wcsftime} or @code{strfmon} style arguments which
 should be type-checked against a format string.  For example, the
 declaration:
 
@@ -2077,16 +2078,19 @@ for consistency with the @code{printf} s
 @code{my_format}.
 
 The parameter @var{archetype} determines how the format string is
-interpreted, and should be @code{printf}, @code{scanf}, @code{strftime}
-or @code{strfmon}.  (You can also use @code{__printf__},
-@code{__scanf__}, @code{__strftime__} or @code{__strfmon__}.)  The
-parameter @var{string-index} specifies which argument is the format
-string argument (starting from 1), while @var{first-to-check} is the
-number of the first argument to check against the format string.  For
-functions where the arguments are not available to be checked (such as
+interpreted, and should be @code{printf}, @code{scanf}, @code{strftime},
+@code{wprintf}, @code{wscanf}, @code{wcsftime} or @code{strfmon}.
+(You can also use @code{__printf__}, @code{__scanf__}, @code{__strftime__},
+@code{__wprintf__}, @code{__wscanf__}, @code{__wcsftime__} or
+@code{__strfmon__}.)  The parameter @var{string-index} specifies which
+argument is the format string argument (starting from 1), while
+@var{first-to-check} is the number of the first argument to check against
+the format string.
+For functions where the arguments are not available to be checked (such as
 @code{vprintf}), specify the third parameter as zero.  In this case the
 compiler only checks the format string for consistency.  For
-@code{strftime} formats, the third parameter is required to be zero.
+@code{strftime} and @code{wcsftime} formats, the third parameter is
+required to be zero.
 
 In the example above, the format string (@code{my_format}) is the second
 argument of the function @code{my_print}, and the arguments to check
@@ -2102,20 +2106,25 @@ for the standard library functions @code
 @code{sprintf}, @code{scanf}, @code{fscanf}, @code{sscanf}, @code{strftime},
 @code{vprintf}, @code{vfprintf} and @code{vsprintf} whenever such
 warnings are requested (using @option{-Wformat}), so there is no need to
-modify the header file @file{stdio.h}.  In C99 mode, the functions
-@code{snprintf}, @code{vsnprintf}, @code{vscanf}, @code{vfscanf} and
-@code{vsscanf} are also checked.  Except in strictly conforming C
-standard modes, the X/Open function @code{strfmon} is also checked.
-@xref{C Dialect Options,,Options Controlling C Dialect}.
+modify the header file @file{stdio.h}.  In C89 Amendment 1 mode,
+the functions @code{swprintf}, @code{wscanf}, @code{fwscanf},
+@code{swscanf}, @code{wcsftime}, @code{vwprintf},
+@code{vfwprintf} and @code{vswprintf} are also checked.
+In C99 mode, the functions @code{snprintf}, @code{vsnprintf},
+@code{vscanf}, @code{vfscanf}, @code{vsscanf}, @code{vwscanf},
+@code{vfwscanf} and @code{vswscanf} are also checked.  Except in
+strictly conforming C standard modes, the X/Open function @code{strfmon}
+is also checked. @xref{C Dialect Options,,Options Controlling C Dialect}.
 
 @item format_arg (@var{string-index})
 @cindex @code{format_arg} function attribute
 @opindex Wformat-nonliteral
 The @code{format_arg} attribute specifies that a function takes a format
-string for a @code{printf}, @code{scanf}, @code{strftime} or
-@code{strfmon} style function and modifies it (for example, to translate
-it into another language), so the result can be passed to a
-@code{printf}, @code{scanf}, @code{strftime} or @code{strfmon} style
+string for a @code{printf}, @code{scanf}, @code{strftime}, @code{wprintf},
+@code{wscanf}, @code{wcsftime} or @code{strfmon} style function and modifies
+it (for example, to translate it into another language), so the result can
+be passed to a @code{printf}, @code{scanf}, @code{strftime}, @code{wprintf},
+@code{wscanf}, @code{wcsftime} or @code{strfmon} style
 function (with the remaining arguments to the format function the same
 as they would have been for the unmodified string).  For example, the
 declaration:
@@ -2130,7 +2139,20 @@ my_dgettext (char *my_domain, const char
 causes the compiler to check the arguments in calls to a @code{printf},
 @code{scanf}, @code{strftime} or @code{strfmon} type function, whose
 format string argument is a call to the @code{my_dgettext} function, for
-consistency with the format string argument @code{my_format}.  If the
+consistency with the format string argument @code{my_format}.  Similarly,
+the declaration:
+
+@smallexample
+extern wchar_t *
+my_wide_translator (int mode, char *language, const wchar_t *my_format)
+      __attribute__ ((format_arg (3)));
+@end smallexample
+
+@noindent
+causes the compiler to check the arguments in calls to a @code{wprintf},
+@code{wscanf} or @code{wcsftime} type function, whoe format string
+argument is a call to the @code{my_wide_translator} function, for
+consistency with the format string argument @code{my_format}. If the
 @code{format_arg} attribute had not been specified, all the compiler
 could tell in such calls to format functions would be that the format
 string argument is not constant; this would generate a warning when
--- gcc/testsuite/gcc.dg/format/format.h.jj	Sun Jan  7 11:44:59 2001
+++ gcc/testsuite/gcc.dg/format/format.h	Fri Dec 14 16:42:52 2001
@@ -81,6 +81,22 @@ extern int vfscanf (FILE *restrict, cons
 extern int vscanf (const char *restrict, va_list);
 extern int vsscanf (const char *restrict, const char *restrict, va_list);
 
+extern int fwprintf (FILE *restrict, const wchar_t *restrict, ...);
+extern int wprintf (const wchar_t *restrict, ...);
+extern int swprintf (wchar_t *restrict, size_t, const wchar_t *restrict, ...);
+extern int vfwprintf (FILE *restrict, const wchar_t *restrict, va_list);
+extern int vwprintf (const wchar_t *restrict, va_list);
+extern int vswprintf (wchar_t *restrict, size_t, const wchar_t *restrict,
+		      va_list);
+
+extern int fwscanf (FILE *restrict, const wchar_t *restrict, ...);
+extern int wscanf (const wchar_t *restrict, ...);
+extern int swscanf (const wchar_t *restrict, const wchar_t *restrict, ...);
+extern int vfwscanf (FILE *restrict, const wchar_t *restrict, va_list);
+extern int vwscanf (const wchar_t *restrict, va_list);
+extern int vswscanf (const wchar_t *restrict, const wchar_t *restrict,
+		     va_list);
+
 extern char *gettext (const char *);
 extern char *dgettext (const char *, const char *);
 extern char *dcgettext (const char *, const char *, int);
@@ -89,5 +105,7 @@ struct tm;
 
 extern size_t strftime (char *restrict, size_t, const char *restrict,
 			const struct tm *restrict);
+extern size_t wcsftime (wchar_t *restrict, size_t, const wchar_t *restrict,
+			const struct tm *restrict);
 
 extern ssize_t strfmon (char *restrict, size_t, const char *restrict, ...);
--- gcc/testsuite/gcc.dg/format/c94-wprintf-1.c.jj	Fri Dec 14 16:44:14 2001
+++ gcc/testsuite/gcc.dg/format/c94-wprintf-1.c	Fri Dec 14 16:50:15 2001
@@ -0,0 +1,233 @@
+/* Test for wprintf formats.  Formats using C94 features, including cases
+   where C94 specifies some aspect of the format to be ignored or where
+   the behaviour is undefined.
+*/
+/* Origin: Joseph Myers <jsm28@cam.ac.uk> */
+/* { dg-do compile } */
+/* { dg-options "-std=iso9899:199409 -pedantic -Wformat" } */
+
+#include "format.h"
+
+void
+foo (int i, int i1, int i2, unsigned int u, double d, char *s, void *p,
+     int *n, short int *hn, long int l, unsigned long int ul,
+     long int *ln, long double ld, wint_t lc, wchar_t *ls, llong ll,
+     ullong ull, unsigned int *un, const int *cn, signed char *ss,
+     unsigned char *us, const signed char *css, unsigned int u1,
+     unsigned int u2)
+{
+  /* See ISO/IEC 9899:1990 (E) subclause 7.9.6.1 (pages 131-134).  */
+  /* Basic sanity checks for the different components of a format.  */
+  wprintf (L"%d\n", i);
+  wprintf (L"%+d\n", i);
+  wprintf (L"%3d\n", i);
+  wprintf (L"%-3d\n", i);
+  wprintf (L"%.7d\n", i);
+  wprintf (L"%+9.4d\n", i);
+  wprintf (L"%.3ld\n", l);
+  wprintf (L"%*d\n", i1, i);
+  wprintf (L"%.*d\n", i2, i);
+  wprintf (L"%*.*ld\n", i1, i2, l);
+  wprintf (L"%d %lu\n", i, ul);
+  /* GCC has objected to the next one in the past, but it is a valid way
+     of specifying zero precision.
+  */
+  wprintf (L"%.e\n", d); /* { dg-bogus "precision" "bogus precision warning" } */
+  /* Bogus use of width.  */
+  wprintf (L"%5n\n", n); /* { dg-warning "width" "width with %n" } */
+  /* Erroneous, ignored or pointless constructs with precision.  */
+  /* Whether negative values for precision may be included in the format
+     string is not entirely clear; presume not, following Clive Feather's
+     proposed resolution to DR#220 against C99.  In any case, such a
+     construct should be warned about.
+  */
+  wprintf (L"%.-5d\n", i); /* { dg-warning "format|precision" "negative precision warning" } */
+  wprintf (L"%.-*d\n", i); /* { dg-warning "format" "broken %.-*d format" } */
+  wprintf (L"%.3c\n", i); /* { dg-warning "precision" "precision with %c" } */
+  wprintf (L"%.3p\n", p); /* { dg-warning "precision" "precision with %p" } */
+  wprintf (L"%.3n\n", n); /* { dg-warning "precision" "precision with %n" } */
+  /* Valid and invalid %% constructions.  Some of the warning messages
+     are non-optimal, but they do detect the errorneous nature of the
+     format string.
+  */
+  wprintf (L"%%");
+  wprintf (L"%.3%"); /* { dg-warning "format" "bogus %%" } */
+  wprintf (L"%-%"); /* { dg-warning "format" "bogus %%" } */
+  wprintf (L"%-%\n"); /* { dg-warning "format" "bogus %%" } */
+  wprintf (L"%5%\n"); /* { dg-warning "format" "bogus %%" } */
+  wprintf (L"%h%\n"); /* { dg-warning "format" "bogus %%" } */
+  /* Valid and invalid %h, %l, %L constructions.  */
+  wprintf (L"%hd", i);
+  wprintf (L"%hi", i);
+  /* Strictly, these parameters should be int or unsigned int according to
+     what unsigned short promotes to.  However, GCC ignores sign
+     differences in format checking here, and this is relied on to get the
+     correct checking without print_char_table needing to know whether
+     int and short are the same size.
+  */
+  wprintf (L"%ho%hu%hx%hX", u, u, u, u);
+  wprintf (L"%hn", hn);
+  wprintf (L"%hf", d); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%he", d); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%hE", d); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%hg", d); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%hG", d); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%hc", i); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%hs", s); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%hp", p); /* { dg-warning "length" "bad use of %h" } */
+  wprintf (L"%h"); /* { dg-warning "conversion lacks type" "bare %h" } */
+  wprintf (L"%h."); /* { dg-warning "conversion" "bogus %h." } */
+  wprintf (L"%ld%li%lo%lu%lx%lX", l, l, ul, ul, ul, ul);
+  wprintf (L"%ln", ln);
+  wprintf (L"%lf", d); /* { dg-warning "length|C" "bad use of %l" } */
+  wprintf (L"%le", d); /* { dg-warning "length|C" "bad use of %l" } */
+  wprintf (L"%lE", d); /* { dg-warning "length|C" "bad use of %l" } */
+  wprintf (L"%lg", d); /* { dg-warning "length|C" "bad use of %l" } */
+  wprintf (L"%lG", d); /* { dg-warning "length|C" "bad use of %l" } */
+  wprintf (L"%lp", p); /* { dg-warning "length|C" "bad use of %l" } */
+  wprintf (L"%lc", lc);
+  wprintf (L"%ls", ls);
+  /* These uses of %L are legitimate, though GCC has wrongly warned for
+     them in the past.
+  */
+  wprintf (L"%Le%LE%Lf%Lg%LG", ld, ld, ld, ld, ld);
+  /* These next six are accepted by GCC as referring to long long,
+     but -pedantic correctly warns.
+  */
+  wprintf (L"%Ld", ll); /* { dg-warning "does not support" "bad use of %L" } */
+  wprintf (L"%Li", ll); /* { dg-warning "does not support" "bad use of %L" } */
+  wprintf (L"%Lo", ull); /* { dg-warning "does not support" "bad use of %L" } */
+  wprintf (L"%Lu", ull); /* { dg-warning "does not support" "bad use of %L" } */
+  wprintf (L"%Lx", ull); /* { dg-warning "does not support" "bad use of %L" } */
+  wprintf (L"%LX", ull); /* { dg-warning "does not support" "bad use of %L" } */
+  wprintf (L"%Lc", i); /* { dg-warning "length" "bad use of %L" } */
+  wprintf (L"%Ls", s); /* { dg-warning "length" "bad use of %L" } */
+  wprintf (L"%Lp", p); /* { dg-warning "length" "bad use of %L" } */
+  wprintf (L"%Ln", n); /* { dg-warning "length" "bad use of %L" } */
+  /* Valid uses of each bare conversion.  */
+  wprintf (L"%d%i%o%u%x%X%f%e%E%g%G%c%s%p%n%%", i, i, u, u, u, u, d, d, d, d, d,
+	   i, s, p, n);
+  /* Uses of the - flag (valid on all non-%, non-n conversions).  */
+  wprintf (L"%-d%-i%-o%-u%-x%-X%-f%-e%-E%-g%-G%-c%-s%-p", i, i, u, u, u, u,
+	   d, d, d, d, d, i, s, p);
+  wprintf (L"%-n", n); /* { dg-warning "flag" "bad use of %-n" } */
+  /* Uses of the + flag (valid on signed conversions only).  */
+  wprintf (L"%+d%+i%+f%+e%+E%+g%+G\n", i, i, d, d, d, d, d);
+  wprintf (L"%+o", u); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+u", u); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+x", u); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+X", u); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+c", i); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+s", s); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+p", p); /* { dg-warning "flag" "bad use of + flag" } */
+  wprintf (L"%+n", n); /* { dg-warning "flag" "bad use of + flag" } */
+  /* Uses of the space flag (valid on signed conversions only, and ignored
+     with +).
+  */
+  wprintf (L"% +d", i); /* { dg-warning "use of both|ignored" "use of space and + flags" } */
+  wprintf (L"%+ d", i); /* { dg-warning "use of both|ignored" "use of space and + flags" } */
+  wprintf (L"% d% i% f% e% E% g% G\n", i, i, d, d, d, d, d);
+  wprintf (L"% o", u); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% u", u); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% x", u); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% X", u); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% c", i); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% s", s); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% p", p); /* { dg-warning "flag" "bad use of space flag" } */
+  wprintf (L"% n", n); /* { dg-warning "flag" "bad use of space flag" } */
+  /* Uses of the # flag.  */
+  wprintf (L"%#o%#x%#X%#e%#E%#f%#g%#G", u, u, u, d, d, d, d, d);
+  wprintf (L"%#d", i); /* { dg-warning "flag" "bad use of # flag" } */
+  wprintf (L"%#i", i); /* { dg-warning "flag" "bad use of # flag" } */
+  wprintf (L"%#u", u); /* { dg-warning "flag" "bad use of # flag" } */
+  wprintf (L"%#c", i); /* { dg-warning "flag" "bad use of # flag" } */
+  wprintf (L"%#s", s); /* { dg-warning "flag" "bad use of # flag" } */
+  wprintf (L"%#p", p); /* { dg-warning "flag" "bad use of # flag" } */
+  wprintf (L"%#n", n); /* { dg-warning "flag" "bad use of # flag" } */
+  /* Uses of the 0 flag.  */
+  wprintf (L"%08d%08i%08o%08u%08x%08X%08e%08E%08f%08g%08G", i, i, u, u, u, u,
+	   d, d, d, d, d);
+  wprintf (L"%0c", i); /* { dg-warning "flag" "bad use of 0 flag" } */
+  wprintf (L"%0s", s); /* { dg-warning "flag" "bad use of 0 flag" } */
+  wprintf (L"%0p", p); /* { dg-warning "flag" "bad use of 0 flag" } */
+  wprintf (L"%0n", n); /* { dg-warning "flag" "bad use of 0 flag" } */
+  /* 0 flag ignored with precision for certain types, not others.  */
+  wprintf (L"%08.5d", i); /* { dg-warning "ignored" "0 flag ignored with precision" } */
+  wprintf (L"%08.5i", i); /* { dg-warning "ignored" "0 flag ignored with precision" } */
+  wprintf (L"%08.5o", u); /* { dg-warning "ignored" "0 flag ignored with precision" } */
+  wprintf (L"%08.5u", u); /* { dg-warning "ignored" "0 flag ignored with precision" } */
+  wprintf (L"%08.5x", u); /* { dg-warning "ignored" "0 flag ignored with precision" } */
+  wprintf (L"%08.5X", u); /* { dg-warning "ignored" "0 flag ignored with precision" } */
+  wprintf (L"%08.5f%08.5e%08.5E%08.5g%08.5G", d, d, d, d, d);
+  /* 0 flag ignored with - flag.  */
+  wprintf (L"%-08d", i); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08i", i); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08o", u); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08u", u); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08x", u); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08X", u); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08e", d); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08E", d); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08f", d); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08g", d); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  wprintf (L"%-08G", d); /* { dg-warning "flags|ignored" "0 flag ignored with - flag" } */
+  /* Various tests of bad argument types.  */
+  wprintf (L"%d", l); /* { dg-warning "format" "bad argument types" } */
+  wprintf (L"%*.*d", l, i2, i); /* { dg-warning "field" "bad * argument types" } */
+  wprintf (L"%*.*d", i1, l, i); /* { dg-warning "field" "bad * argument types" } */
+  wprintf (L"%ld", i); /* { dg-warning "format" "bad argument types" } */
+  wprintf (L"%s", n); /* { dg-warning "format" "bad argument types" } */
+  wprintf (L"%p", i); /* { dg-warning "format" "bad argument types" } */
+  wprintf (L"%n", p); /* { dg-warning "format" "bad argument types" } */
+  /* With -pedantic, we want some further checks for pointer targets:
+     %p should allow only pointers to void (possibly qualified) and
+     to character types (possibly qualified), but not function pointers
+     or pointers to other types.  (Whether, in fact, character types are
+     allowed here is unclear; see thread on comp.std.c, July 2000 for
+     discussion of the requirements of rules on identical representation,
+     and of the application of the as if rule with the new va_arg
+     allowances in C99 to wprintf.)  Likewise, we should warn if
+     pointer targets differ in signedness, except in some circumstances
+     for character pointers.  (In C99 we should consider warning for
+     char * or unsigned char * being passed to %hhn, even if strictly
+     legitimate by the standard.)
+  */
+  wprintf (L"%p", foo); /* { dg-warning "format" "bad argument types" } */
+  wprintf (L"%n", un); /* { dg-warning "format" "bad argument types" } */
+  wprintf (L"%p", n); /* { dg-warning "format" "bad argument types" } */
+  /* Allow character pointers with %p.  */
+  wprintf (L"%p%p%p%p", s, ss, us, css);
+  /* %s allows any character type.  */
+  wprintf (L"%s%s%s%s", s, ss, us, css);
+  /* Warning for void * arguments for %s is GCC's historical behaviour,
+     and seems useful to keep, even if some standard versions might be
+     read to permit it.
+  */
+  wprintf (L"%s", p); /* { dg-warning "format" "bad argument types" } */
+  /* The historical behaviour is to allow signed / unsigned types
+     interchangably as arguments.  For values representable in both types,
+     such usage may be correct.  For now preserve the behaviour of GCC
+     in such cases.
+  */
+  wprintf (L"%d", u);
+  /* Also allow the same for width and precision arguments.  In the past,
+     GCC has been inconsistent and allowed unsigned for width but not
+     precision.
+  */
+  wprintf (L"%*.*d", u1, u2, i);
+  /* Wrong number of arguments.  */
+  wprintf (L"%d%d", i); /* { dg-warning "arguments" "wrong number of args" } */
+  wprintf (L"%d", i, i); /* { dg-warning "arguments" "wrong number of args" } */
+  /* Miscellaneous bogus constructions.  */
+  wprintf (L""); /* { dg-warning "zero-length" "warning for empty format" } */
+  wprintf (L"\0"); /* { dg-warning "embedded" "warning for embedded NUL" } */
+  wprintf (L"%d\0", i); /* { dg-warning "embedded" "warning for embedded NUL" } */
+  wprintf (L"%d\0%d", i, i); /* { dg-warning "embedded|too many" "warning for embedded NUL" } */
+  wprintf (NULL); /* { dg-warning "null" "null format string warning" } */
+  wprintf (L"%"); /* { dg-warning "trailing" "trailing % warning" } */
+  wprintf (L"%++d", i); /* { dg-warning "repeated" "repeated flag warning" } */
+  wprintf (L"%n", cn); /* { dg-warning "constant" "%n with const" } */
+  wprintf ((const wchar_t *)"foo"); /* { dg-warning "not a wide" "non-wide string" } */
+  wprintf (L"%n", (int *)0); /* { dg-warning "null" "%n with NULL" } */
+  wprintf (L"%s", (char *)0); /* { dg-warning "null" "%s with NULL" } */
+}
--- gcc/testsuite/gcc.dg/format/c94-wscanf-1.c.jj	Fri Dec 14 16:52:09 2001
+++ gcc/testsuite/gcc.dg/format/c94-wscanf-1.c	Fri Dec 14 16:55:31 2001
@@ -0,0 +1,116 @@
+/* Test for wscanf formats.  Formats using C94 features, including cases
+   where C94 specifies some aspect of the format to be ignored or where
+   the behaviour is undefined.
+*/
+/* Origin: Joseph Myers <jsm28@cam.ac.uk> */
+/* { dg-do compile } */
+/* { dg-options "-std=iso9899:199409 -pedantic -Wformat" } */
+
+#include "format.h"
+
+void
+foo (int *ip, unsigned int *uip, short int *hp, unsigned short int *uhp,
+     long int *lp, unsigned long int *ulp, float *fp, double *dp,
+     long double *ldp, char *s, signed char *ss, unsigned char *us,
+     void **pp, int *n, llong *llp, ullong *ullp, wchar_t *ls,
+     const int *cip, const int *cn, const char *cs, const void **ppc,
+     void *const *pcp, short int *hn, long int *ln, void *p, char **sp,
+     volatile void *ppv)
+{
+  /* See ISO/IEC 9899:1990 (E) subclause 7.9.6.2 (pages 134-138).  */
+  /* Basic sanity checks for the different components of a format.  */
+  wscanf (L"%d", ip);
+  wscanf (L"%*d");
+  wscanf (L"%3d", ip);
+  wscanf (L"%hd", hp);
+  wscanf (L"%3ld", lp);
+  wscanf (L"%*3d");
+  wscanf (L"%d %ld", ip, lp);
+  /* Valid and invalid %% constructions.  */
+  wscanf (L"%%");
+  wscanf (L"%*%"); /* { dg-warning "format" "bogus %%" } */
+  wscanf (L"%*%\n"); /* { dg-warning "format" "bogus %%" } */
+  wscanf (L"%4%"); /* { dg-warning "format" "bogus %%" } */
+  wscanf (L"%4%\n"); /* { dg-warning "format" "bogus %%" } */
+  wscanf (L"%h%"); /* { dg-warning "format" "bogus %%" } */
+  wscanf (L"%h%\n"); /* { dg-warning "format" "bogus %%" } */
+  /* Valid, invalid and silly assignment-suppression constructions.  */
+  wscanf (L"%*d%*i%*o%*u%*x%*X%*e%*E%*f%*g%*G%*s%*[abc]%*c%*p");
+  wscanf (L"%*2d%*8s%*3c");
+  wscanf (L"%*n", n); /* { dg-warning "suppress" "suppression of %n" } */
+  wscanf (L"%*hd"); /* { dg-warning "together" "suppression with length" } */
+  /* Valid, invalid and silly width constructions.  */
+  wscanf (L"%2d%3i%4o%5u%6x%7X%8e%9E%10f%11g%12G%13s%14[abc]%15c%16p",
+	  ip, ip, uip, uip, uip, uip, fp, fp, fp, fp, fp, s, s, s, pp);
+  wscanf (L"%0d", ip); /* { dg-warning "width" "warning for zero width" } */
+  wscanf (L"%3n", n); /* { dg-warning "width" "width with %n" } */
+  /* Valid and invalid %h, %l, %L constructions.  */
+  wscanf (L"%hd%hi%ho%hu%hx%hX%hn", hp, hp, uhp, uhp, uhp, uhp, hn);
+  wscanf (L"%he", fp); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hE", fp); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hf", fp); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hg", fp); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hG", fp); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hs", s); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%h[ac]", s); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hc", s); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%hp", pp); /* { dg-warning "length" "bad use of %h" } */
+  wscanf (L"%h"); /* { dg-warning "conversion lacks type" "bare %h" } */
+  wscanf (L"%h."); /* { dg-warning "conversion" "bogus %h" } */
+  wscanf (L"%ld%li%lo%lu%lx%lX%ln", lp, lp, ulp, ulp, ulp, ulp, ln);
+  wscanf (L"%le%lE%lf%lg%lG", dp, dp, dp, dp, dp);
+  wscanf (L"%lp", pp); /* { dg-warning "length" "bad use of %l" } */
+  wscanf (L"%lc%ls%l[abc]", ls, ls, ls);
+  wscanf (L"%Le%LE%Lf%Lg%LG", ldp, ldp, ldp, ldp, ldp);
+  wscanf (L"%Ld", llp); /* { dg-warning "does not support" "bad use of %L" } */
+  wscanf (L"%Li", llp); /* { dg-warning "does not support" "bad use of %L" } */
+  wscanf (L"%Lo", ullp); /* { dg-warning "does not support" "bad use of %L" } */
+  wscanf (L"%Lu", ullp); /* { dg-warning "does not support" "bad use of %L" } */
+  wscanf (L"%Lx", ullp); /* { dg-warning "does not support" "bad use of %L" } */
+  wscanf (L"%LX", ullp); /* { dg-warning "does not support" "bad use of %L" } */
+  wscanf (L"%Ls", s); /* { dg-warning "length" "bad use of %L" } */
+  wscanf (L"%L[ac]", s); /* { dg-warning "length" "bad use of %L" } */
+  wscanf (L"%Lc", s); /* { dg-warning "length" "bad use of %L" } */
+  wscanf (L"%Lp", pp); /* { dg-warning "length" "bad use of %L" } */
+  wscanf (L"%Ln", n); /* { dg-warning "length" "bad use of %L" } */
+  /* Valid uses of each bare conversion.  */
+  wscanf (L"%d%i%o%u%x%X%e%E%f%g%G%s%[abc]%c%p%n%%", ip, ip, uip, uip, uip,
+	  uip, fp, fp, fp, fp, fp, s, s, s, pp, n);
+  /* Allow other character pointers with %s, %c, %[].  */
+  wscanf (L"%2s%3s%4c%5c%6[abc]%7[abc]", ss, us, ss, us, ss, us);
+  /* Further tests for %[].  */
+  wscanf (L"%[%d]%d", s, ip);
+  wscanf (L"%[^%d]%d", s, ip);
+  wscanf (L"%[]%d]%d", s, ip);
+  wscanf (L"%[^]%d]%d", s, ip);
+  wscanf (L"%[%d]%d", s, ip);
+  wscanf (L"%[]abcd", s); /* { dg-warning "no closing" "incomplete scanset" } */
+  /* Various tests of bad argument types.  Some of these are only pedantic
+     warnings.
+  */
+  wscanf (L"%d", lp); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%d", uip); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%d", pp); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%p", ppc); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%p", ppv); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%s", n); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%s", p); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%p", p); /* { dg-warning "format" "bad argument types" } */
+  wscanf (L"%p", sp); /* { dg-warning "format" "bad argument types" } */
+  /* Tests for writing into constant values.  */
+  wscanf (L"%d", cip); /* { dg-warning "constant" "%d writing into const" } */
+  wscanf (L"%n", cn); /* { dg-warning "constant" "%n writing into const" } */
+  wscanf (L"%s", cs); /* { dg-warning "constant" "%s writing into const" } */
+  wscanf (L"%p", pcp); /* { dg-warning "constant" "%p writing into const" } */
+  /* Wrong number of arguments.  */
+  wscanf (L"%d%d", ip); /* { dg-warning "arguments" "wrong number of args" } */
+  wscanf (L"%d", ip, ip); /* { dg-warning "arguments" "wrong number of args" } */
+  /* Miscellaneous bogus constructions.  */
+  wscanf (L""); /* { dg-warning "zero-length" "warning for empty format" } */
+  wscanf (L"\0"); /* { dg-warning "embedded" "warning for embedded NUL" } */
+  wscanf (L"%d\0", ip); /* { dg-warning "embedded" "warning for embedded NUL" } */
+  wscanf (L"%d\0%d", ip, ip); /* { dg-warning "embedded|too many" "warning for embedded NUL" } */
+  wscanf (NULL); /* { dg-warning "null" "null format string warning" } */
+  wscanf (L"%"); /* { dg-warning "trailing" "trailing % warning" } */
+  wscanf (L"%d", (int *)0); /* { dg-warning "null" "writing into NULL" } */
+}
--- gcc/testsuite/gcc.dg/format/c94-wcsftime-1.c.jj	Fri Dec 14 16:56:04 2001
+++ gcc/testsuite/gcc.dg/format/c94-wcsftime-1.c	Fri Dec 14 16:57:27 2001
@@ -0,0 +1,19 @@
+/* Test for wcsftime formats.  Formats using C94 features.  */
+/* Origin: Joseph Myers <jsm28@cam.ac.uk> */
+/* { dg-do compile } */
+/* { dg-options "-std=iso9899:199409 -pedantic -Wformat" } */
+
+#include "format.h"
+
+void
+foo (wchar_t *s, size_t m, const struct tm *tp)
+{
+  /* See ISO/IEC 9899:1990 (E) subclause 7.12.3.5 (pages 174-175).  */
+  /* Formats which are Y2K-compliant (no 2-digit years).  */
+  wcsftime (s, m, L"%a%A%b%B%d%H%I%j%m%M%p%S%U%w%W%X%Y%Z%%", tp);
+  /* Formats with 2-digit years.  */
+  wcsftime (s, m, L"%y", tp); /* { dg-warning "only last 2" "2-digit year" } */
+  /* Formats with 2-digit years in some locales.  */
+  wcsftime (s, m, L"%c", tp); /* { dg-warning "some locales" "2-digit year" } */
+  wcsftime (s, m, L"%x", tp); /* { dg-warning "some locales" "2-digit year" } */
+}
--- gcc/testsuite/gcc.dg/format/wbranch-1.c.jj	Fri Dec 14 16:58:49 2001
+++ gcc/testsuite/gcc.dg/format/wbranch-1.c	Fri Dec 14 19:29:54 2001
@@ -0,0 +1,28 @@
+/* Test for format checking of conditional expressions for wide
+   character strings.  */
+/* Origin: Joseph Myers <jsm28@cam.ac.uk> */
+/* { dg-do compile } */
+/* { dg-options "-std=gnu99 -Wformat" } */
+
+#include "format.h"
+
+void
+foo (long l, int nfoo)
+{
+  wprintf ((nfoo > 1) ? L"%d foos" : L"%d foo", nfoo);
+  wprintf ((l > 1) ? L"%d foos" : L"%d foo", l); /* { dg-warning "int format" "wrong type in conditional expr" } */
+  wprintf ((l > 1) ? L"%ld foos" : L"%d foo", l); /* { dg-warning "int format" "wrong type in conditional expr" } */
+  wprintf ((l > 1) ? L"%d foos" : L"%ld foo", l); /* { dg-warning "int format" "wrong type in conditional expr" } */
+  /* Should allow one case to have extra arguments.  */
+  wprintf ((nfoo > 1) ? L"%d foos" : L"1 foo", nfoo);
+  wprintf ((nfoo > 1) ? L"many foos" : L"1 foo", nfoo); /* { dg-warning "too many" "too many args in all branches" } */
+  wprintf ((nfoo > 1) ? L"%d foos" : L"", nfoo);
+  wprintf ((nfoo > 1) ? L"%d foos" : ((nfoo > 0) ? L"1 foo" : L"no foos"), nfoo);
+  wprintf ((nfoo > 1) ? L"%d foos" : ((nfoo > 0) ? L"%d foo" : L"%d foos"), nfoo);
+  wprintf ((nfoo > 1) ? L"%d foos" : ((nfoo > 0) ? L"%d foo" : L"%ld foos"), nfoo); /* { dg-warning "long int format" "wrong type" } */
+  wprintf ((nfoo > 1) ? L"%ld foos" : ((nfoo > 0) ? L"%d foo" : L"%d foos"), nfoo); /* { dg-warning "long int format" "wrong type" } */
+  wprintf ((nfoo > 1) ? L"%d foos" : ((nfoo > 0) ? L"%ld foo" : L"%d foos"), nfoo); /* { dg-warning "long int format" "wrong type" } */
+  /* Extra arguments to NULL should be complained about.  */
+  wprintf (NULL, L"foo"); /* { dg-warning "too many" "NULL extra args" } */
+  /* { dg-warning "null" "null format arg" { target *-*-* } 26 } */
+}
--- gcc/builtin-attrs.def.jj	Tue Oct  2 09:12:20 2001
+++ gcc/builtin-attrs.def	Fri Dec 14 17:09:52 2001
@@ -80,6 +80,9 @@ DEF_LIST_INT_INT (3,4)
 DEF_ATTR_IDENT (ATTR_PRINTF, "printf")
 DEF_ATTR_IDENT (ATTR_SCANF, "scanf")
 DEF_ATTR_IDENT (ATTR_STRFTIME, "strftime")
+DEF_ATTR_IDENT (ATTR_WPRINTF, "wprintf")
+DEF_ATTR_IDENT (ATTR_WSCANF, "wscanf")
+DEF_ATTR_IDENT (ATTR_WCSFTIME, "wcsftime")
 DEF_ATTR_IDENT (ATTR_STRFMON, "strfmon")
 
 DEF_ATTR_IDENT (ATTR_FORMAT, "format")
@@ -102,6 +105,17 @@ DEF_FORMAT_ATTRIBUTE(SCANF,1_2)
 DEF_FORMAT_ATTRIBUTE(SCANF,2_0)
 DEF_FORMAT_ATTRIBUTE(SCANF,2_3)
 DEF_FORMAT_ATTRIBUTE(STRFTIME,3_0)
+DEF_FORMAT_ATTRIBUTE(WPRINTF,1_0)
+DEF_FORMAT_ATTRIBUTE(WPRINTF,1_2)
+DEF_FORMAT_ATTRIBUTE(WPRINTF,2_0)
+DEF_FORMAT_ATTRIBUTE(WPRINTF,2_3)
+DEF_FORMAT_ATTRIBUTE(WPRINTF,3_0)
+DEF_FORMAT_ATTRIBUTE(WPRINTF,3_4)
+DEF_FORMAT_ATTRIBUTE(WSCANF,1_0)
+DEF_FORMAT_ATTRIBUTE(WSCANF,1_2)
+DEF_FORMAT_ATTRIBUTE(WSCANF,2_0)
+DEF_FORMAT_ATTRIBUTE(WSCANF,2_3)
+DEF_FORMAT_ATTRIBUTE(WCSFTIME,3_0)
 DEF_FORMAT_ATTRIBUTE(STRFMON,3_4)
 #undef DEF_FORMAT_ATTRIBUTE
 
@@ -140,6 +154,23 @@ DEF_C89_ATTR (vsprintf, ATTR_FORMAT_PRIN
 DEF_C89_ATTR (strftime, ATTR_FORMAT_STRFTIME_3_0)
 #undef DEF_C89_ATTR
 
+/* ISO/IEC9899:1990/Amendment 1:1994 functions.  */
+#define DEF_C94_ATTR(NAME, ATTRS)					    \
+  DEF_FN_ATTR_IDENT (NAME, ATTRS,					    \
+	       (flag_hosted						    \
+		&& (flag_isoc94 || flag_noniso_default_format_attributes)))
+DEF_C94_ATTR (fwprintf, ATTR_FORMAT_WPRINTF_2_3)
+DEF_C94_ATTR (fwscanf, ATTR_FORMAT_WSCANF_2_3)
+DEF_C94_ATTR (swprintf, ATTR_FORMAT_WPRINTF_3_4)
+DEF_C94_ATTR (swscanf, ATTR_FORMAT_WSCANF_2_3)
+DEF_C94_ATTR (vfwprintf, ATTR_FORMAT_WPRINTF_2_0)
+DEF_C94_ATTR (vswprintf, ATTR_FORMAT_WPRINTF_3_0)
+DEF_C94_ATTR (vwprintf, ATTR_FORMAT_WPRINTF_1_0)
+DEF_C94_ATTR (wcsftime, ATTR_FORMAT_WCSFTIME_3_0)
+DEF_C94_ATTR (wprintf, ATTR_FORMAT_WPRINTF_1_2)
+DEF_C94_ATTR (wscanf, ATTR_FORMAT_WSCANF_1_2)
+#undef DEF_C94_ATTR
+
 /* ISO C99 adds the snprintf and vscanf family functions.  */
 #define DEF_C99_ATTR(NAME, ATTRS)					    \
   DEF_FN_ATTR_IDENT (NAME, ATTRS,					    \
@@ -150,6 +181,9 @@ DEF_C99_ATTR (vsnprintf, ATTR_FORMAT_PRI
 DEF_C99_ATTR (vscanf, ATTR_FORMAT_SCANF_1_0)
 DEF_C99_ATTR (vfscanf, ATTR_FORMAT_SCANF_2_0)
 DEF_C99_ATTR (vsscanf, ATTR_FORMAT_SCANF_2_0)
+DEF_C99_ATTR (vwscanf, ATTR_FORMAT_WSCANF_1_0)
+DEF_C99_ATTR (vfwscanf, ATTR_FORMAT_WSCANF_2_0)
+DEF_C99_ATTR (vswscanf, ATTR_FORMAT_WSCANF_2_0)
 #undef DEF_C99_ATTR
 
 /* Functions not in any version of ISO C.  */

	Jakub


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