Include <ctype.h>; avoid home-grown ISDIGIT.
authorRichard Kenner <kenner@gcc.gnu.org>
Tue, 5 Oct 1993 19:14:50 +0000 (15:14 -0400)
committerRichard Kenner <kenner@gcc.gnu.org>
Tue, 5 Oct 1993 19:14:50 +0000 (15:14 -0400)
(decl_attributes): Call record_function_format with DECL_NAME and also
DECL_ASSEMBLER_NAME.
(struct format_char_info): Moved here from c-typeck.c.
(print_char_table): Likewise; renamed from print_table.
(scan_char_table): Likewise; renamed from scan_table.
(struct function_format_info): Likewise; add assembler_name.
(function_format_list): Make list, not array.
(init_function_format_info): Likewise; renamed from init_format_info_table.
(record_function_format): Likewise; renamed from record_format_info;
record assembler_name.
(check_format_info): Likewise; renamed from check_format.
(check_function_format): New interface that does not require knowledge of
struct function_format_info.

From-SVN: r5607

gcc/c-common.c

index 3bdc7140ad3fa39401f67b6ebfea618e1783898d..bd70adc821cd42c7151a5d282e018e09ca541b83 100644 (file)
@@ -24,6 +24,7 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #include "flags.h"
 #include "obstack.h"
 #include <stdio.h>
+#include <ctype.h>
 
 extern struct obstack permanent_obstack;
 
@@ -335,12 +336,577 @@ decl_attributes (decl, attributes)
                return;
              }
          }
-       
-       record_format_info (DECL_NAME (decl), is_scan, format_num,
-                           first_arg_num);
+
+       record_function_format (DECL_NAME (decl), DECL_ASSEMBLER_NAME (decl),
+                               is_scan, format_num, first_arg_num);
       }
 }
 \f
+/* Check a printf/fprintf/sprintf/scanf/fscanf/sscanf format against
+   a parameter list.  */
+
+#define T_I    &integer_type_node
+#define T_L    &long_integer_type_node
+#define T_S    &short_integer_type_node
+#define T_UI   &unsigned_type_node
+#define T_UL   &long_unsigned_type_node
+#define T_US   &short_unsigned_type_node
+#define T_F    &float_type_node
+#define T_D    &double_type_node
+#define T_LD   &long_double_type_node
+#define T_C    &char_type_node
+#define T_V    &void_type_node
+#define T_W    &wchar_type_node
+
+typedef struct {
+  char *format_chars;
+  int pointer_count;
+  /* Type of argument if no length modifier is used.  */
+  tree *nolen;
+  /* Type of argument if length modifier for shortening is used.
+     If NULL, then this modifier is not allowed.  */
+  tree *hlen;
+  /* Type of argument if length modifier `l' is used.
+     If NULL, then this modifier is not allowed.  */
+  tree *llen;
+  /* Type of argument if length modifier `L' is used.
+     If NULL, then this modifier is not allowed.  */
+  tree *bigllen;
+  /* List of other modifier characters allowed with these options.  */
+  char *flag_chars;
+} format_char_info;
+
+static format_char_info print_char_table[] = {
+  { "di",      0,      T_I,    T_I,    T_L,    NULL,   "-wp0 +"        },
+  { "oxX",     0,      T_UI,   T_UI,   T_UL,   NULL,   "-wp0#"         },
+  { "u",       0,      T_UI,   T_UI,   T_UL,   NULL,   "-wp0"          },
+  { "feEgG",   0,      T_D,    NULL,   NULL,   T_LD,   "-wp0 +#"       },
+  { "c",       0,      T_I,    NULL,   T_W,    NULL,   "-w"            },
+  { "C",       0,      T_W,    NULL,   NULL,   NULL,   "-w"            },
+  { "s",       1,      T_C,    NULL,   T_W,    NULL,   "-wp"           },
+  { "S",       1,      T_W,    NULL,   NULL,   NULL,   "-wp"           },
+  { "p",       1,      T_V,    NULL,   NULL,   NULL,   "-w"            },
+  { "n",       1,      T_I,    T_S,    T_L,    NULL,   ""              },
+  { NULL }
+};
+
+static format_char_info scan_char_table[] = {
+  { "di",      1,      T_I,    T_S,    T_L,    NULL,   "*"     },
+  { "ouxX",    1,      T_UI,   T_US,   T_UL,   NULL,   "*"     },      
+  { "efgEG",   1,      T_F,    NULL,   T_D,    T_LD,   "*"     },
+  { "sc",      1,      T_C,    NULL,   T_W,    NULL,   "*"     },
+  { "[",       1,      T_C,    NULL,   NULL,   NULL,   "*"     },
+  { "C",       1,      T_W,    NULL,   NULL,   NULL,   "*"     },
+  { "S",       1,      T_W,    NULL,   NULL,   NULL,   "*"     },
+  { "p",       2,      T_V,    NULL,   NULL,   NULL,   "*"     },
+  { "n",       1,      T_I,    T_S,    T_L,    NULL,   ""      },
+  { NULL }
+};
+
+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++) */
+  int is_scan;                 /* TRUE if *scanf */
+  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;
+
+static void check_format_info PROTO((function_format_info *, tree));
+
+/* Initialize the table of functions to perform format checking on.
+   The ANSI 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 ANSI reserves these function names whether you include the
+   header file or not.  In any case, the checking is harmless.  */
+
+void
+init_function_format_info ()
+{
+  record_function_format (get_identifier ("printf"), NULL_TREE, 0, 1, 2);
+  record_function_format (get_identifier ("fprintf"), NULL_TREE, 0, 2, 3);
+  record_function_format (get_identifier ("sprintf"), NULL_TREE, 0, 2, 3);
+  record_function_format (get_identifier ("scanf"), NULL_TREE, 1, 1, 2);
+  record_function_format (get_identifier ("fscanf"), NULL_TREE, 1, 2, 3);
+  record_function_format (get_identifier ("sscanf"), NULL_TREE, 1, 2, 3);
+  record_function_format (get_identifier ("vprintf"), NULL_TREE, 0, 1, 0);
+  record_function_format (get_identifier ("vfprintf"), NULL_TREE, 0, 2, 0);
+  record_function_format (get_identifier ("vsprintf"), NULL_TREE, 0, 2, 0);
+}
+
+/* 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).  IS_SCAN is true for scanf-type format checking;
+   false indicates printf-style 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 teh format string, or zero if no checking is not be done
+   (e.g. for varargs such as vfprintf).  */
+
+void
+record_function_format (name, assembler_name, is_scan,
+                       format_num, first_arg_num)
+      tree name;
+      tree assembler_name;
+      int is_scan;
+      int format_num;
+      int first_arg_num;
+{
+  function_format_info *info;
+
+  /* Re-use existing structure if it's there.  */
+
+  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;
+    }
+
+  info->is_scan = is_scan;
+  info->format_num = format_num;
+  info->first_arg_num = first_arg_num;
+}
+
+static char    tfaff[] = "too few arguments for format";
+\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.)
+   PARAMS is the list of argument values.  */
+
+void
+check_function_format (name, assembler_name, params)
+     tree name;
+     tree assembler_name;
+     tree params;
+{
+  function_format_info *info;
+
+  /* See if this function is a format function.  */
+  for (info = function_format_list; info; info = info->next)
+    {
+      if ((info->assembler_name || assembler_name)
+         ? (info->assembler_name == assembler_name)
+         : (info->name == name))
+       {
+         /* Yup; check it.  */
+         check_format_info (info, params);
+         break;
+       }
+    }
+}
+
+/* Check the argument list of a call to printf, scanf, etc.
+   INFO points to the function_format_info structure.
+   PARAMS is the list of argument values.  */
+
+static void
+check_format_info (info, params)
+     function_format_info *info;
+     tree params;
+{
+  int i;
+  int arg_num;
+  int suppressed, wide, precise;
+  int length_char;
+  int format_char;
+  int format_length;
+  tree format_tree;
+  tree cur_param;
+  tree cur_type;
+  tree wanted_type;
+  char *format_chars;
+  format_char_info *fci;
+  static char message[132];
+  char flag_chars[8];
+
+  /* Skip to format argument.  If the argument isn't available, there's
+     no work for us to do; prototype checking will catch the problem.  */
+  for (arg_num = 1; ; ++arg_num)
+    {
+      if (params == 0)
+       return;
+      if (arg_num == info->format_num)
+       break;
+      params = TREE_CHAIN (params);
+    }
+  format_tree = TREE_VALUE (params);
+  params = TREE_CHAIN (params);
+  if (format_tree == 0)
+    return;
+  /* We can only check the format if it's a string constant.  */
+  while (TREE_CODE (format_tree) == NOP_EXPR)
+    format_tree = TREE_OPERAND (format_tree, 0); /* strip coercion */
+  if (format_tree == null_pointer_node)
+    {
+      warning ("null format string");
+      return;
+    }
+  if (TREE_CODE (format_tree) != ADDR_EXPR)
+    return;
+  format_tree = TREE_OPERAND (format_tree, 0);
+  if (TREE_CODE (format_tree) != STRING_CST)
+    return;
+  format_chars = TREE_STRING_POINTER (format_tree);
+  format_length = TREE_STRING_LENGTH (format_tree);
+  if (format_length <= 1)
+    warning ("zero-length format string");
+  if (format_chars[--format_length] != 0)
+    {
+      warning ("unterminated format string");
+      return;
+    }
+  /* Skip to first argument to check.  */
+  while (arg_num + 1 < info->first_arg_num)
+    {
+      if (params == 0)
+       return;
+      params = TREE_CHAIN (params);
+      ++arg_num;
+    }
+  while (1)
+    {
+      if (*format_chars == 0)
+       {
+         if (format_chars - TREE_STRING_POINTER (format_tree) != format_length)
+           warning ("embedded `\\0' in format");
+         if (info->first_arg_num != 0 && params != 0)
+           warning ("too many arguments for format");
+         return;
+       }
+      if (*format_chars++ != '%')
+       continue;
+      if (*format_chars == 0)
+       {
+         warning ("spurious trailing `%%' in format");
+         continue;
+       }
+      if (*format_chars == '%')
+       {
+         ++format_chars;
+         continue;
+       }
+      flag_chars[0] = 0;
+      suppressed = wide = precise = FALSE;
+      if (info->is_scan)
+       {
+         suppressed = *format_chars == '*';
+         if (suppressed)
+           ++format_chars;
+         while (isdigit (*format_chars))
+           ++format_chars;
+       }
+      else
+       {
+         while (*format_chars != 0 && index (" +#0-", *format_chars) != 0)
+           {
+             if (index (flag_chars, *format_chars) != 0)
+               {
+                 sprintf (message, "repeated `%c' flag in format",
+                          *format_chars);
+                 warning (message);
+               }
+             i = strlen (flag_chars);
+             flag_chars[i++] = *format_chars++;
+             flag_chars[i] = 0;
+           }
+         /* "If the space and + flags both appear, 
+            the space flag will be ignored."  */
+         if (index (flag_chars, ' ') != 0
+             && index (flag_chars, '+') != 0)
+           warning ("use of both ` ' and `+' flags in format");
+         /* "If the 0 and - flags both appear,
+            the 0 flag will be ignored."  */
+         if (index (flag_chars, '0') != 0
+             && index (flag_chars, '-') != 0)
+           warning ("use of both `0' and `-' flags in format");
+         if (*format_chars == '*')
+           {
+             wide = TRUE;
+             /* "...a field width...may be indicated by an asterisk.
+                In this case, an int argument supplies the field width..."  */
+             ++format_chars;
+             if (params == 0)
+               {
+                 warning (tfaff);
+                 return;
+               }
+             if (info->first_arg_num != 0)
+               {
+                 cur_param = TREE_VALUE (params);
+                 params = TREE_CHAIN (params);
+                 ++arg_num;
+                 /* size_t is generally not valid here.
+                    It will work on most machines, because size_t and int
+                    have the same mode.  But might as well warn anyway,
+                    since it will fail on other machines.  */
+                 if (TYPE_MAIN_VARIANT (TREE_TYPE (cur_param))
+                     != integer_type_node)
+                   {
+                     sprintf (message,
+                              "field width is not type int (arg %d)",
+                              arg_num);
+                     warning (message);
+                   }
+               }
+           }
+         else
+           {
+             while (isdigit (*format_chars))
+               {
+                 wide = TRUE;
+                 ++format_chars;
+               }
+           }
+         if (*format_chars == '.')
+           {
+             precise = TRUE;
+             ++format_chars;
+             if (*format_chars != '*' && !isdigit (*format_chars))
+               warning ("`.' not followed by `*' or digit in format");
+             /* "...a...precision...may be indicated by an asterisk.
+                In this case, an int argument supplies the...precision."  */
+             if (*format_chars == '*')
+               {
+                 if (info->first_arg_num != 0)
+                   {
+                     ++format_chars;
+                     if (params == 0)
+                       {
+                         warning (tfaff);
+                         return;
+                       }
+                     cur_param = TREE_VALUE (params);
+                     params = TREE_CHAIN (params);
+                     ++arg_num;
+                     if (TYPE_MAIN_VARIANT (TREE_TYPE (cur_param))
+                         != integer_type_node)
+                       {
+                         sprintf (message,
+                                  "field width is not type int (arg %d)",
+                                  arg_num);
+                         warning (message);
+                       }
+                   }
+               }
+             else
+               {
+                 while (isdigit (*format_chars))
+                   ++format_chars;
+               }
+           }
+       }
+      if (*format_chars == 'h' || *format_chars == 'l' || *format_chars == 'L')
+       length_char = *format_chars++;
+      else
+       length_char = 0;
+      if (suppressed && length_char != 0)
+       {
+         sprintf (message,
+                  "use of `*' and `%c' together in format",
+                  length_char);
+         warning (message);
+       }
+      format_char = *format_chars;
+      if (format_char == 0)
+       {
+         warning ("conversion lacks type at end of format");
+         continue;
+       }
+      format_chars++;
+      fci = info->is_scan ? scan_char_table : print_char_table;
+      while (fci->format_chars != 0
+            && index (fci->format_chars, format_char) == 0)
+         ++fci;
+      if (fci->format_chars == 0)
+       {
+         if (format_char >= 040 && format_char < 0177)
+           sprintf (message,
+                    "unknown conversion type character `%c' in format",
+                    format_char);
+         else
+           sprintf (message,
+                    "unknown conversion type character 0x%x in format",
+                    format_char);
+         warning (message);
+         continue;
+       }
+      if (wide && index (fci->flag_chars, 'w') == 0)
+       {
+         sprintf (message, "width used with `%c' format",
+                  format_char);
+         warning (message);
+       }
+      if (precise && index (fci->flag_chars, 'p') == 0)
+       {
+         sprintf (message, "precision used with `%c' format",
+                  format_char);
+         warning (message);
+       }
+      if (info->is_scan && format_char == '[')
+       {
+         /* Skip over scan set, in case it happens to have '%' in it.  */
+         if (*format_chars == '^')
+           ++format_chars;
+         /* Find closing bracket; if one is hit immediately, then
+            it's part of the scan set rather than a terminator.  */
+         if (*format_chars == ']')
+           ++format_chars;
+         while (*format_chars && *format_chars != ']')
+           ++format_chars;
+         if (*format_chars != ']')
+             /* The end of the format string was reached.  */
+             warning ("no closing `]' for `%%[' format");
+       }
+      if (suppressed)
+       {
+         if (index (fci->flag_chars, '*') == 0)
+           {
+             sprintf (message,
+                      "suppression of `%c' conversion in format",
+                      format_char);
+             warning (message);
+           }
+         continue;
+       }
+      for (i = 0; flag_chars[i] != 0; ++i)
+       {
+         if (index (fci->flag_chars, flag_chars[i]) == 0)
+           {
+             sprintf (message, "flag `%c' used with type `%c'",
+                      flag_chars[i], format_char);
+             warning (message);
+           }
+       }
+      if (precise && index (flag_chars, '0') != 0
+         && (format_char == 'd' || format_char == 'i'
+             || format_char == 'o' || format_char == 'u'
+             || format_char == 'x' || format_char == 'x'))
+       {
+         sprintf (message,
+                  "precision and `0' flag not both allowed with `%c' format",
+                  format_char);
+         warning (message);
+       }
+      switch (length_char)
+       {
+       default: wanted_type = fci->nolen ? *(fci->nolen) : 0; break;
+       case 'h': wanted_type = fci->hlen ? *(fci->hlen) : 0; break;
+       case 'l': wanted_type = fci->llen ? *(fci->llen) : 0; break;
+       case 'L': wanted_type = fci->bigllen ? *(fci->bigllen) : 0; break;
+       }
+      if (wanted_type == 0)
+       {
+         sprintf (message,
+                  "use of `%c' length character with `%c' type character",
+                  length_char, format_char);
+         warning (message);
+       }
+
+      /*
+       ** XXX -- should kvetch about stuff such as
+       **      {
+       **              const int       i;
+       **
+       **              scanf ("%d", &i);
+       **      }
+       */
+
+      /* Finally. . .check type of argument against desired type!  */
+      if (info->first_arg_num == 0)
+       continue;
+      if (params == 0)
+       {
+         warning (tfaff);
+         return;
+       }
+      cur_param = TREE_VALUE (params);
+      params = TREE_CHAIN (params);
+      ++arg_num;
+      cur_type = TREE_TYPE (cur_param);
+
+      /* Check the types of any additional pointer arguments
+        that precede the "real" argument.  */
+      for (i = 0; i < fci->pointer_count; ++i)
+       {
+         if (TREE_CODE (cur_type) == POINTER_TYPE)
+           {
+             cur_type = TREE_TYPE (cur_type);
+             continue;
+           }
+         sprintf (message,
+                  "format argument is not a %s (arg %d)",
+                  ((fci->pointer_count == 1) ? "pointer" : "pointer to a pointer"),
+                  arg_num);
+         warning (message);
+         break;
+       }
+
+      /* Check the type of the "real" argument, if there's a type we want.  */
+      if (i == fci->pointer_count && wanted_type != 0
+         && wanted_type != TYPE_MAIN_VARIANT (cur_type)
+         /* If we want `void *', allow any pointer type.
+            (Anything else would already have got a warning.)  */
+         && ! (wanted_type == void_type_node
+               && fci->pointer_count > 0)
+         /* Don't warn about differences merely in signedness.  */
+         && !(TREE_CODE (wanted_type) == INTEGER_TYPE
+              && TREE_CODE (cur_type) == INTEGER_TYPE
+              && (wanted_type == (TREE_UNSIGNED (wanted_type)
+                                  ? unsigned_type : signed_type) (cur_type))))
+       {
+         register char *this;
+         register char *that;
+  
+         this = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (wanted_type)));
+         that = 0;
+         if (TREE_CODE (cur_type) != ERROR_MARK
+             && TYPE_NAME (cur_type) != 0
+             && TREE_CODE (cur_type) != INTEGER_TYPE
+             && !(TREE_CODE (cur_type) == POINTER_TYPE
+                  && TREE_CODE (TREE_TYPE (cur_type)) == INTEGER_TYPE))
+           {
+             if (TREE_CODE (TYPE_NAME (cur_type)) == TYPE_DECL
+                 && DECL_NAME (TYPE_NAME (cur_type)) != 0)
+               that = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (cur_type)));
+             else
+               that = IDENTIFIER_POINTER (TYPE_NAME (cur_type));
+           }
+
+         /* A nameless type can't possibly match what the format wants.
+            So there will be a warning for it.
+            Make up a string to describe vaguely what it is.  */
+         if (that == 0)
+           {
+             if (TREE_CODE (cur_type) == POINTER_TYPE)
+               that = "pointer";
+             else
+               that = "different type";
+           }
+
+         if (strcmp (this, that) != 0)
+           {
+             sprintf (message, "%s format, %s arg (arg %d)",
+                       this, that, arg_num);
+             warning (message);
+           }
+       }
+    }
+}
+\f
 /* Print a warning if a constant expression had overflow in folding.
    Invoke this function on every expression that the language
    requires to be a constant expression.