OSDN Git Service

2002-06-21 Matt Thomas <matt@3am-software.com>
[pf3gnuchains/gcc-fork.git] / gcc / c-format.c
index 061ddb9..99892f9 100644 (file)
@@ -1,6 +1,6 @@
 /* Check calls to formatted I/O functions (-Wformat).
-   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
-   Free Software Foundation, Inc.
+   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
+   2001, 2002 Free Software Foundation, Inc.
 
 This file is part of GCC.
 
@@ -27,7 +27,7 @@ Software Foundation, 59 Temple Place - Suite 330, Boston, MA
 #include "c-common.h"
 #include "intl.h"
 #include "diagnostic.h"
-
+#include "langhooks.h"
 \f
 /* Command line options and their associated flags.  */
 
@@ -44,6 +44,10 @@ int warn_format_y2k;
 
 int warn_format_extra_args;
 
+/* Warn about zero-length formats.  */
+
+int warn_format_zero_length;
+
 /* Warn about non-literal format arguments.  */
 
 int warn_format_nonliteral;
@@ -61,11 +65,15 @@ set_Wformat (setting)
   warn_format = setting;
   warn_format_y2k = setting;
   warn_format_extra_args = setting;
+  warn_format_zero_length = setting;
   if (setting != 1)
     {
       warn_format_nonliteral = setting;
       warn_format_security = setting;
     }
+  /* Make sure not to disable -Wnonnull if -Wformat=0 is specified.  */
+  if (setting)
+    warn_nonnull = setting;
 }
 
 \f
@@ -374,7 +382,10 @@ enum
   /* Zero width is bad in this type of format (scanf).  */
   FMT_FLAG_ZERO_WIDTH_BAD = 32,
   /* Empty precision specification is OK in this type of format (printf).  */
-  FMT_FLAG_EMPTY_PREC_OK = 64
+  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
   /* 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
@@ -399,7 +410,7 @@ typedef struct
 } 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
 {
@@ -420,7 +431,7 @@ 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
 {
@@ -639,12 +650,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 }
 };
 
@@ -848,7 +859,7 @@ static const format_kind_info format_types[] =
   },
   { "scanf",    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_ARG_CONVERT|FMT_FLAG_SCANF_A_KLUDGE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_ZERO_WIDTH_BAD|FMT_FLAG_DOLLAR_GAP_POINTER_OK,
     'w', 0, 0, '*', 'L',
     NULL, NULL
   },
@@ -892,10 +903,16 @@ typedef struct
   int number_other;
 } format_check_results;
 
+typedef struct
+{
+  format_check_results *res;
+  function_format_info *info;
+  tree params;
+  int *status;
+} format_check_context;
+
 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, unsigned HOST_WIDE_INT));
+static void check_format_arg   PARAMS ((void *, tree, unsigned HOST_WIDE_INT));
 static void check_format_info_main PARAMS ((int *, format_check_results *,
                                            function_format_info *,
                                            const char *, int, tree,
@@ -907,7 +924,7 @@ 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 *));
-static void finish_dollar_format_checking      PARAMS ((int *, format_check_results *));
+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 *));
@@ -1008,7 +1025,7 @@ check_function_format (status, attrs, params)
 static void
 status_warning VPARAMS ((int *status, const char *msgid, ...))
 {
-  diagnostic_context dc;
+  diagnostic_info diagnostic ;
 
   VA_OPEN (ap, msgid);
   VA_FIXEDARG (ap, int *, status);
@@ -1019,9 +1036,9 @@ status_warning VPARAMS ((int *status, const char *msgid, ...))
   else
     {
       /* This duplicates the warning function behavior.  */
-      set_diagnostic_context
-       (&dc, msgid, &ap, input_filename, lineno, /* warn = */ 1);
-      report_diagnostic (&dc);
+      diagnostic_set_info (&diagnostic, _(msgid), &ap, input_filename, lineno,
+                           DK_WARNING);
+      report_diagnostic (&diagnostic);
     }
 
   VA_CLOSE (ap);
@@ -1029,6 +1046,7 @@ status_warning VPARAMS ((int *status, const char *msgid, ...))
 
 /* Variables used by the checking of $ operand number formats.  */
 static char *dollar_arguments_used = NULL;
+static char *dollar_arguments_pointer_p = NULL;
 static int dollar_arguments_alloc = 0;
 static int dollar_arguments_count;
 static int dollar_first_arg_num;
@@ -1046,6 +1064,8 @@ init_dollar_format_checking (first_arg_num, params)
      int first_arg_num;
      tree params;
 {
+  tree oparams = params;
+
   dollar_first_arg_num = first_arg_num;
   dollar_arguments_count = 0;
   dollar_max_arg_used = 0;
@@ -1062,11 +1082,28 @@ init_dollar_format_checking (first_arg_num, params)
     {
       if (dollar_arguments_used)
        free (dollar_arguments_used);
+      if (dollar_arguments_pointer_p)
+       free (dollar_arguments_pointer_p);
       dollar_arguments_alloc = dollar_arguments_count;
       dollar_arguments_used = xmalloc (dollar_arguments_alloc);
+      dollar_arguments_pointer_p = xmalloc (dollar_arguments_alloc);
     }
   if (dollar_arguments_alloc)
-    memset (dollar_arguments_used, 0, dollar_arguments_alloc);
+    {
+      memset (dollar_arguments_used, 0, dollar_arguments_alloc);
+      if (first_arg_num > 0)
+       {
+         int i = 0;
+         params = oparams;
+         while (params)
+           {
+             dollar_arguments_pointer_p[i] = (TREE_CODE (TREE_TYPE (TREE_VALUE (params)))
+                                              == POINTER_TYPE);
+             params = TREE_CHAIN (params);
+             i++;
+           }
+       }
+    }
 }
 
 
@@ -1092,7 +1129,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)
        {
@@ -1104,7 +1141,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');
@@ -1146,6 +1183,8 @@ maybe_read_dollar_number (status, format, dollar_needed, params, param_ptr,
       int nalloc;
       nalloc = 2 * dollar_arguments_alloc + 16;
       dollar_arguments_used = xrealloc (dollar_arguments_used, nalloc);
+      dollar_arguments_pointer_p = xrealloc (dollar_arguments_pointer_p,
+                                            nalloc);
       memset (dollar_arguments_used + dollar_arguments_alloc, 0,
              nalloc - dollar_arguments_alloc);
       dollar_arguments_alloc = nalloc;
@@ -1186,21 +1225,32 @@ maybe_read_dollar_number (status, format, dollar_needed, params, param_ptr,
    and for unused operands at the end of the format (if we know how many
    arguments the format had, so not for vprintf).  If there were operand
    numbers out of range on a non-vprintf-style format, we won't have reached
-   here.  */
+   here.  If POINTER_GAP_OK, unused arguments are OK if all arguments are
+   pointers.  */
 
 static void
-finish_dollar_format_checking (status, res)
+finish_dollar_format_checking (status, res, pointer_gap_ok)
      int *status;
      format_check_results *res;
+     int pointer_gap_ok;
 {
   int i;
+  bool found_pointer_gap = false;
   for (i = 0; i < dollar_max_arg_used; i++)
     {
       if (!dollar_arguments_used[i])
-       status_warning (status, "format argument %d unused before used argument %d in $-style format",
-                i + 1, dollar_max_arg_used);
+       {
+         if (pointer_gap_ok && (dollar_first_arg_num == 0
+                                || dollar_arguments_pointer_p[i]))
+           found_pointer_gap = true;
+         else
+           status_warning (status, "format argument %d unused before used argument %d in $-style format",
+                           i + 1, dollar_max_arg_used);
+       }
     }
-  if (dollar_first_arg_num && dollar_max_arg_used < dollar_arguments_count)
+  if (found_pointer_gap
+      || (dollar_first_arg_num
+         && dollar_max_arg_used < dollar_arguments_count))
     {
       res->number_other--;
       res->number_dollar_extra_args++;
@@ -1253,6 +1303,7 @@ check_format_info (status, info, params)
      function_format_info *info;
      tree params;
 {
+  format_check_context format_ctx;
   unsigned HOST_WIDE_INT arg_num;
   tree format_tree;
   format_check_results res;
@@ -1279,7 +1330,13 @@ check_format_info (status, info, params)
   res.number_unterminated = 0;
   res.number_other = 0;
 
-  check_format_info_recurse (status, &res, info, format_tree, params, arg_num);
+  format_ctx.res = &res;
+  format_ctx.info = info;
+  format_ctx.params = params;
+  format_ctx.status = status;
+
+  check_function_arguments_recurse (check_format_arg, &format_ctx,
+                                   format_tree, arg_num);
 
   if (res.number_non_literal > 0)
     {
@@ -1325,8 +1382,9 @@ check_format_info (status, info, params)
       && res.number_other == 0 && warn_format_extra_args)
     status_warning (status, "unused arguments in $-style format");
   if (res.number_empty > 0 && res.number_non_literal == 0
-      && res.number_other == 0)
-    status_warning (status, "zero-length format string");
+      && res.number_other == 0 && warn_format_zero_length)
+    status_warning (status, "zero-length %s format string",
+                   format_types[info->format_type].name);
 
   if (res.number_wide > 0)
     status_warning (status, "format is a wide character string");
@@ -1335,112 +1393,31 @@ check_format_info (status, info, params)
     status_warning (status, "unterminated format string");
 }
 
-
-/* Recursively check a call to a format function.  FORMAT_TREE is the
-   format parameter, which may be a conditional expression in which
-   both halves should be checked.  ARG_NUM is the number of the
-   format argument; PARAMS points just after it in the argument list.  */
+/* Callback from check_function_arguments_recurse to check a
+   format string.  FORMAT_TREE is the format parameter.  ARG_NUM
+   is the number of the format argument.  CTX points to a
+   format_check_context.  */
 
 static void
-check_format_info_recurse (status, res, info, format_tree, params, arg_num)
-     int *status;
-     format_check_results *res;
-     function_format_info *info;
+check_format_arg (ctx, format_tree, arg_num)
+     void *ctx;
      tree format_tree;
-     tree params;
      unsigned HOST_WIDE_INT arg_num;
 {
+  format_check_context *format_ctx = ctx;
+  format_check_results *res = format_ctx->res;
+  function_format_info *info = format_ctx->info;
+  tree params = format_ctx->params;
+  int *status = format_ctx->status;
+
   int format_length;
   HOST_WIDE_INT offset;
   const char *format_chars;
   tree array_size = 0;
   tree array_init;
 
-  if (TREE_CODE (format_tree) == NOP_EXPR)
-    {
-      /* Strip coercion.  */
-      check_format_info_recurse (status, res, info,
-                                TREE_OPERAND (format_tree, 0), params,
-                                arg_num);
-      return;
-    }
-
-  if (TREE_CODE (format_tree) == CALL_EXPR)
-    {
-      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.  Such a function may have multiple
-        format_arg attributes (for example, ngettext).  */
-
-      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 == format_num)
-               {
-                 check_format_info_recurse (status, res, info,
-                                            TREE_VALUE (inner_args), params,
-                                            arg_num);
-                 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)
-    {
-      /* Check both halves of the conditional expression.  */
-      check_format_info_recurse (status, res, info,
-                                TREE_OPERAND (format_tree, 1), params,
-                                arg_num);
-      check_format_info_recurse (status, res, info,
-                                TREE_OPERAND (format_tree, 2), params,
-                                arg_num);
-      return;
-    }
-
   if (integer_zerop (format_tree))
     {
-      /* FIXME: this warning should go away once Marc Espie's
-        __attribute__((nonnull)) patch is in.  Instead, checking for
-        nonnull attributes should probably change this function to act
-        specially if info == NULL and add a res->number_null entry for
-        that case, or maybe add a function pointer to be called at
-        the end instead of hardcoding check_format_info_main.  */
-      status_warning (status, "null format string");
-
       /* Skip to first argument to check, so we can see if this format
         has any arguments (it shouldn't).  */
       while (arg_num + 1 < info->first_arg_num)
@@ -1480,13 +1457,12 @@ check_format_info_recurse (status, res, info, format_tree, params, arg_num)
          res->number_non_literal++;
          return;
        }
-      if (!host_integerp (arg1, 1))
+      if (!host_integerp (arg1, 0)
+         || (offset = tree_low_cst (arg1, 0)) < 0)
        {
          res->number_non_literal++;
          return;
        }
-
-      offset = TREE_INT_CST_LOW (arg1);
     }
   if (TREE_CODE (format_tree) != ADDR_EXPR)
     {
@@ -1639,7 +1615,7 @@ check_format_info_main (status, res, info, format_chars, format_length,
              res->number_extra_args++;
            }
          if (has_operand_number > 0)
-           finish_dollar_format_checking (status, res);
+           finish_dollar_format_checking (status, res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
          return;
        }
       if (*format_chars++ != '%')
@@ -1716,11 +1692,6 @@ check_format_info_main (status, res, info, format_chars, format_length,
              /* "...a field width...may be indicated by an asterisk.
                 In this case, an int argument supplies the field width..."  */
              ++format_chars;
-             if (params == 0)
-               {
-                 status_warning (status, "too few arguments for format");
-                 return;
-               }
              if (has_operand_number != 0)
                {
                  int opnum;
@@ -1740,6 +1711,11 @@ check_format_info_main (status, res, info, format_chars, format_length,
                }
              if (info->first_arg_num != 0)
                {
+                 if (params == 0)
+                   {
+                     status_warning (status, "too few arguments for format");
+                     return;
+                   }
                  cur_param = TREE_VALUE (params);
                  if (has_operand_number <= 0)
                    {
@@ -2223,7 +2199,6 @@ check_format_types (status, types)
       tree cur_type;
       tree orig_cur_type;
       tree wanted_type;
-      tree promoted_type;
       int arg_num;
       int i;
       int char_type_flag;
@@ -2242,11 +2217,7 @@ check_format_types (status, types)
        abort ();
 
       if (types->pointer_count == 0)
-       {
-         promoted_type = simple_type_promotes_to (wanted_type);
-         if (promoted_type != NULL_TREE)
-           wanted_type = promoted_type;
-       }
+       wanted_type = (*lang_hooks.types.type_promotes_to) (wanted_type);
 
       STRIP_NOPS (cur_param);
 
@@ -2351,8 +2322,8 @@ check_format_types (status, types)
          && TREE_CODE (cur_type) == INTEGER_TYPE
          && (! pedantic || i == 0 || (i == 1 && char_type_flag))
          && (TREE_UNSIGNED (wanted_type)
-             ? wanted_type == unsigned_type (cur_type)
-             : wanted_type == signed_type (cur_type)))
+             ? wanted_type == c_common_unsigned_type (cur_type)
+             : wanted_type == c_common_signed_type (cur_type)))
        continue;
       /* Likewise, "signed char", "unsigned char" and "char" are
         equivalent but the above test won't consider them equivalent.  */
@@ -2385,9 +2356,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.  */