PR tree-optimization/80523 - -Wformat-overflow doesn't consider -fexec-charset
authorMartin Sebor <msebor@redhat.com>
Fri, 28 Apr 2017 22:29:40 +0000 (22:29 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Fri, 28 Apr 2017 22:29:40 +0000 (16:29 -0600)
gcc/ChangeLog:

PR tree-optimization/80523
* gimple-ssa-sprintf.c (target_to_host_charmap): New global variable.
(init_target_to_host_charmap, target_to_host, target_strtol10): New
functions.
(maybe_warn, format_directive, parse_directive): Use new functions.
(pass_sprintf_length::execute): Call init_target_to_host_charmap.

gcc/testsuite/ChangeLog:

PR tree-optimization/80523
* gcc.dg/tree-ssa/builtin-sprintf-warn-18.c: New test.

From-SVN: r247401

gcc/ChangeLog
gcc/c-family/c.opt
gcc/gimple-ssa-sprintf.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-18.c [new file with mode: 0644]

index d1204f18311eca1f656a3642c817976e0aa7e936..f8486784f4c0f05e2480d5e0ef104a7eb62e5334 100644 (file)
@@ -1,3 +1,12 @@
+2017-04-28  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/80523
+       * gimple-ssa-sprintf.c (target_to_host_charmap): New global variable.
+       (init_target_to_host_charmap, target_to_host, target_strtol10): New
+       functions.
+       (maybe_warn, format_directive, parse_directive): Use new functions.
+       (pass_sprintf_length::execute): Call init_target_to_host_charmap.       
+
 2017-04-28  Marc Glisse  <marc.glisse@inria.fr>
 
        * match.pd (X+Z OP Y+Z, X-Z OP Y-Z, Z-X OP Z-Y): New transformations.
index 6ecbfca44137aefe71fe6315f40fe805c4f92b81..9ad2f6e1fcc54b48cead4c9f2609ac04a233d1cc 100644 (file)
@@ -304,7 +304,7 @@ C ObjC C++ ObjC++ Var(warn_alloca) Warning
 Warn on any use of alloca.
 
 Walloc-size-larger-than=
-C ObjC C++ ObjC++ Var(warn_alloc_size_limit) Warning Joined
+C ObjC C++ ObjC++ Var(warn_alloc_size_limit) Warning Joined LangEnabledBy(C ObjC C++ ObjC++,Wall)
 -Walloc-size-larger-than=<bytes> Warn for calls to allocation functions that
 attempt to allocate objects larger than the specified number of bytes.
 
@@ -716,7 +716,7 @@ Warn about buffer overflow in string manipulation functions like memcpy
 and strcpy.
 
 Wstringop-overflow=
-C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_stringop_overflow) Init(2) Warning LangEnabledBy(C ObjC C++ ObjC++, Wall)
 Under the control of Object Size type, warn about buffer overflow in string
 manipulation functions like memcpy and strcpy.
 
index d3771ddcc67b5eb77dfa61c169b32b1b88a332e7..c3c717d9060273594d9626611000cc35e4b89257 100644 (file)
@@ -66,6 +66,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "calls.h"
 #include "cfgloop.h"
 #include "intl.h"
+#include "langhooks.h"
 
 #include "builtins.h"
 #include "stor-layout.h"
@@ -273,6 +274,158 @@ target_size_max ()
   return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
 }
 
+/* A straightforward mapping from the execution character set to the host
+   character set indexed by execution character.  */
+
+static char target_to_host_charmap[256];
+
+/* Initialize a mapping from the execution character set to the host
+   character set.  */
+
+static bool
+init_target_to_host_charmap ()
+{
+  /* If the percent sign is non-zero the mapping has already been
+     initialized.  */
+  if (target_to_host_charmap['%'])
+    return true;
+
+  /* Initialize the target_percent character (done elsewhere).  */
+  if (!init_target_chars ())
+    return false;
+
+  /* The subset of the source character set used by printf conversion
+     specifications (strictly speaking, not all letters are used but
+     they are included here for the sake of simplicity).  The dollar
+     sign must be included even though it's not in the basic source
+     character set.  */
+  const char srcset[] = " 0123456789!\"#%&'()*+,-./:;<=>?[\\]^_{|}~$"
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+  /* Set the mapping for all characters to some ordinary value (i,e.,
+     not none used in printf conversion specifications) and overwrite
+     those that are used by conversion specifications with their
+     corresponding values.  */
+  memset (target_to_host_charmap + 1, '?', sizeof target_to_host_charmap - 1);
+
+  /* Are the two sets of characters the same?  */
+  bool all_same_p = true;
+
+  for (const char *pc = srcset; *pc; ++pc)
+    {
+      /* Slice off the high end bits in case target characters are
+        signed.  All values are expected to be non-nul, otherwise
+        there's a problem.  */
+      if (unsigned char tc = lang_hooks.to_target_charset (*pc))
+       {
+         target_to_host_charmap[tc] = *pc;
+         if (tc != *pc)
+           all_same_p = false;
+       }
+      else
+       return false;
+
+    }
+
+  /* Set the first element to a non-zero value if the mapping
+     is 1-to-1, otherwise leave it clear (NUL is assumed to be
+     the same in both character sets).  */
+  target_to_host_charmap[0] = all_same_p;
+
+  return true;
+}
+
+/* Return the host source character corresponding to the character
+   CH in the execution character set if one exists, or some innocuous
+   (non-special, non-nul) source character otherwise.  */
+
+static inline unsigned char
+target_to_host (unsigned char ch)
+{
+  return target_to_host_charmap[ch];
+}
+
+/* Convert an initial substring of the string TARGSTR consisting of
+   characters in the execution character set into a string in the
+   source character set on the host and store up to HOSTSZ characters
+   in the buffer pointed to by HOSTR.  Return HOSTR.  */
+
+static const char*
+target_to_host (char *hostr, size_t hostsz, const char *targstr)
+{
+  /* Make sure the buffer is reasonably big.  */
+  gcc_assert (hostsz > 4);
+
+  /* The interesting subset of source and execution characters are
+     the same so no conversion is necessary.  However, truncate
+     overlong strings just like the translated strings are.  */
+  if (target_to_host_charmap['\0'] == 1)
+    {
+      strncpy (hostr, targstr, hostsz - 4);
+      if (strlen (targstr) >= hostsz)
+       strcpy (hostr + hostsz - 4, "...");
+      return hostr;
+    }
+
+  /* Convert the initial substring of TARGSTR to the corresponding
+     characters in the host set, appending "..." if TARGSTR is too
+     long to fit.  Using the static buffer assumes the function is
+     not called in between sequence points (which it isn't).  */
+  for (char *ph = hostr; ; ++targstr)
+    {
+      *ph++ = target_to_host (*targstr);
+      if (!*targstr)
+       break;
+
+      if (size_t (ph - hostr) == hostsz - 4)
+       {
+         *ph = '\0';
+         strcat (ph, "...");
+         break;
+       }
+    }
+
+  return hostr;
+}
+
+/* Convert the sequence of decimal digits in the execution character
+   starting at S to a long, just like strtol does.  Return the result
+   and set *END to one past the last converted character.  On range
+   error set ERANGE to the digit that caused it.  */
+
+static inline long
+target_strtol10 (const char **ps, const char **erange)
+{
+  unsigned HOST_WIDE_INT val = 0;
+  for ( ; ; ++*ps)
+    {
+      unsigned char c = target_to_host (**ps);
+      if (ISDIGIT (c))
+       {
+         c -= '0';
+
+         /* Check for overflow.  */
+         if (val > (LONG_MAX - c) / 10LU)
+           {
+             val = LONG_MAX;
+             *erange = *ps;
+
+             /* Skip the remaining digits.  */
+             do
+               c = target_to_host (*++*ps);
+             while (ISDIGIT (c));
+             break;
+           }
+         else
+           val = val * 10 + c;
+       }
+      else
+       break;
+    }
+
+  return val;
+}
+
 /* Return the constant initial value of DECL if available or DECL
    otherwise.  Same as the synonymous function in c/c-typeck.c.  */
 
@@ -2284,12 +2437,16 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
                    || (res.max < HOST_WIDE_INT_MAX
                        && avail_range.min < res.max)));
 
+  /* Buffer for the directive in the host character set (used when
+     the source character set is different).  */
+  char hostdir[32];
+
   if (avail_range.min == avail_range.max)
     {
       /* The size of the destination region is exact.  */
       unsigned HOST_WIDE_INT navail = avail_range.max;
 
-      if (*dir.beg != '%')
+      if (target_to_host (*dir.beg) != '%')
        {
          /* For plain character directives (i.e., the format string itself)
             but not others, point the caret at the first character that's
@@ -2339,9 +2496,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
                  : G_("%<%.*s%> directive writing %wu bytes "
                       "into a region of size %wu")));
          return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg, res.min,
-                         navail);
+                         info.warnopt (), fmtstr, dir.len,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         res.min, navail);
        }
 
       if (res.min == 0 && res.max < maxbytes)
@@ -2356,8 +2513,8 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
               : G_("%<%.*s%> directive writing up to %wu bytes "
                    "into a region of size %wu"));
          return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg,
+                         info.warnopt (), fmtstr, dir.len,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
                          res.max, navail);
        }
 
@@ -2376,8 +2533,8 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
               : G_("%<%.*s%> directive writing likely %wu or more bytes "
                    "into a region of size %wu"));
          return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg,
+                         info.warnopt (), fmtstr, dir.len,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
                          res.likely, navail);
        }
 
@@ -2393,10 +2550,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
               : G_("%<%.*s%> directive writing between %wu and "
                    "%wu bytes into a region of size %wu"));
          return fmtwarn (dirloc, pargrange, NULL,
-                         info.warnopt (), fmtstr,
-                         dir.len, dir.beg,
-                         res.min, res.max,
-                         navail);
+                         info.warnopt (), fmtstr, dir.len,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         res.min, res.max, navail);
        }
 
       const char* fmtstr
@@ -2409,14 +2565,14 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
           : G_("%<%.*s%> directive writing %wu or more bytes "
                "into a region of size %wu"));
       return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg,
+                     info.warnopt (), fmtstr, dir.len,
+                     target_to_host (hostdir, sizeof hostdir, dir.beg),
                      res.min, navail);
     }
 
   /* The size of the destination region is a range.  */
 
-  if (*dir.beg != '%')
+  if (target_to_host (*dir.beg) != '%')
     {
       unsigned HOST_WIDE_INT navail = avail_range.max;
 
@@ -2468,9 +2624,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
                   "into a region of size between %wu and %wu")));
 
       return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg, res.min,
-                     avail_range.min, avail_range.max);
+                     info.warnopt (), fmtstr, dir.len,
+                     target_to_host (hostdir, sizeof hostdir, dir.beg),
+                     res.min, avail_range.min, avail_range.max);
     }
 
   if (res.min == 0 && res.max < maxbytes)
@@ -2487,9 +2643,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
           : G_("%<%.*s%> directive writing up to %wu bytes "
                "into a region of size between %wu and %wu"));
       return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg, res.max,
-                     avail_range.min, avail_range.max);
+                     info.warnopt (), fmtstr, dir.len,
+                     target_to_host (hostdir, sizeof hostdir, dir.beg),
+                     res.max, avail_range.min, avail_range.max);
     }
 
   if (res.min == 0 && maxbytes <= res.max)
@@ -2509,9 +2665,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
           : G_("%<%.*s%> directive writing likely %wu or more bytes "
                "into a region of size between %wu and %wu"));
       return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg, res.likely,
-                     avail_range.min, avail_range.max);
+                     info.warnopt (), fmtstr, dir.len,
+                     target_to_host (hostdir, sizeof hostdir, dir.beg),
+                     res.likely, avail_range.min, avail_range.max);
     }
 
   if (res.max < maxbytes)
@@ -2528,10 +2684,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
           : G_("%<%.*s%> directive writing between %wu and "
                "%wu bytes into a region of size between %wu and %wu"));
       return fmtwarn (dirloc, pargrange, NULL,
-                     info.warnopt (), fmtstr,
-                     dir.len, dir.beg,
-                     res.min, res.max,
-                     avail_range.min, avail_range.max);
+                     info.warnopt (), fmtstr, dir.len,
+                     target_to_host (hostdir, sizeof hostdir, dir.beg),
+                     res.min, res.max, avail_range.min, avail_range.max);
     }
 
   const char* fmtstr
@@ -2546,10 +2701,9 @@ maybe_warn (substring_loc &dirloc, source_range *pargrange,
        : G_("%<%.*s%> directive writing %wu or more bytes "
            "into a region of size between %wu and %wu"));
   return fmtwarn (dirloc, pargrange, NULL,
-                 info.warnopt (), fmtstr,
-                 dir.len, dir.beg,
-                 res.min,
-                 avail_range.min, avail_range.max);
+                 info.warnopt (), fmtstr, dir.len,
+                 target_to_host (hostdir, sizeof hostdir, dir.beg),
+                 res.min, avail_range.min, avail_range.max);
 }
 
 /* Compute the length of the output resulting from the directive DIR
@@ -2630,13 +2784,17 @@ format_directive (const pass_sprintf_length::call_info &info,
        }
     }
 
+  /* Buffer for the directive in the host character set (used when
+     the source character set is different).  */
+  char hostdir[32];
+
   int dirlen = dir.len;
 
   if (fmtres.nullp)
     {
       fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
               "%<%.*s%> directive argument is null",
-              dirlen, dir.beg);
+              dirlen, target_to_host (hostdir, sizeof hostdir, dir.beg));
 
       /* Don't bother processing the rest of the format string.  */
       res->warned = true;
@@ -2703,7 +2861,9 @@ format_directive (const pass_sprintf_length::call_info &info,
                          info.warnopt (),
                          "%<%.*s%> directive output of %wu bytes exceeds "
                          "minimum required size of 4095",
-                         dirlen, dir.beg, fmtres.range.min);
+                         dirlen,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         fmtres.range.min);
       else
        {
          const char *fmtstr
@@ -2714,8 +2874,8 @@ format_directive (const pass_sprintf_length::call_info &info,
                    "bytes exceeds minimum required size of 4095"));
 
          warned = fmtwarn (dirloc, pargrange, NULL,
-                           info.warnopt (), fmtstr,
-                           dirlen, dir.beg,
+                           info.warnopt (), fmtstr, dirlen,
+                           target_to_host (hostdir, sizeof hostdir, dir.beg),
                            fmtres.range.min, fmtres.range.max);
        }
     }
@@ -2744,7 +2904,9 @@ format_directive (const pass_sprintf_length::call_info &info,
        warned = fmtwarn (dirloc, pargrange, NULL, info.warnopt (),
                          "%<%.*s%> directive output of %wu bytes causes "
                          "result to exceed %<INT_MAX%>",
-                         dirlen, dir.beg, fmtres.range.min);
+                         dirlen,
+                         target_to_host (hostdir, sizeof hostdir, dir.beg),
+                         fmtres.range.min);
       else
        {
          const char *fmtstr
@@ -2754,8 +2916,8 @@ format_directive (const pass_sprintf_length::call_info &info,
               : G_ ("%<%.*s%> directive output between %wu and %wu "
                     "bytes may cause result to exceed %<INT_MAX%>"));
          warned = fmtwarn (dirloc, pargrange, NULL,
-                           info.warnopt (), fmtstr,
-                           dirlen, dir.beg,
+                           info.warnopt (), fmtstr, dirlen,
+                           target_to_host (hostdir, sizeof hostdir, dir.beg),
                            fmtres.range.min, fmtres.range.max);
        }
     }
@@ -2847,7 +3009,7 @@ parse_directive (pass_sprintf_length::call_info &info,
                 directive &dir, format_result *res,
                 const char *str, unsigned *argno)
 {
-  const char *pcnt = strchr (str, '%');
+  const char *pcnt = strchr (str, target_percent);
   dir.beg = str;
 
   if (size_t len = pcnt ? pcnt - str : *str ? strlen (str) : 1)
@@ -2873,7 +3035,7 @@ parse_directive (pass_sprintf_length::call_info &info,
   const char *pf = pcnt + 1;
 
     /* POSIX numbered argument index or zero when none.  */
-  unsigned dollar = 0;
+  HOST_WIDE_INT dollar = 0;
 
   /* With and precision.  -1 when not specified, HOST_WIDE_INT_MIN
      when given by a va_list argument, and a non-negative value
@@ -2881,6 +3043,17 @@ parse_directive (pass_sprintf_length::call_info &info,
   HOST_WIDE_INT width = -1;
   HOST_WIDE_INT precision = -1;
 
+  /* Pointers to the beginning of the width and precision decimal
+     string (if any) within the directive.  */
+  const char *pwidth = 0;
+  const char *pprec = 0;
+
+  /* When the value of the decimal string that specifies width or
+     precision is out of range, points to the digit that causes
+     the value to exceed the limit.  */
+  const char *werange = NULL;
+  const char *perange = NULL;
+
   /* Width specified via the asterisk.  Need not be INTEGER_CST.
      For vararg functions set to void_node.  */
   tree star_width = NULL_TREE;
@@ -2889,17 +3062,16 @@ parse_directive (pass_sprintf_length::call_info &info,
      For vararg functions set to void_node.  */
   tree star_precision = NULL_TREE;
 
-  if (ISDIGIT (*pf))
+  if (ISDIGIT (target_to_host (*pf)))
     {
       /* This could be either a POSIX positional argument, the '0'
         flag, or a width, depending on what follows.  Store it as
         width and sort it out later after the next character has
         been seen.  */
-      char *end;
-      width = strtol (pf, &end, 10);
-      pf = end;
+      pwidth = pf;
+      width = target_strtol10 (&pf, &werange);
     }
-  else if ('*' == *pf)
+  else if (target_to_host (*pf) == '*')
     {
       /* Similarly to the block above, this could be either a POSIX
         positional argument or a width, depending on what follows.  */
@@ -2910,7 +3082,7 @@ parse_directive (pass_sprintf_length::call_info &info,
       ++pf;
     }
 
-  if (*pf == '$')
+  if (target_to_host (*pf) == '$')
     {
       /* Handle the POSIX dollar sign which references the 1-based
         positional argument number.  */
@@ -2925,7 +3097,7 @@ parse_directive (pass_sprintf_length::call_info &info,
       /* Bail when the numbered argument is out of range (it will
         have already been diagnosed by -Wformat).  */
       if (dollar == 0
-         || dollar == info.argidx
+         || dollar == (int)info.argidx
          || dollar > gimple_call_num_args (info.callstmt))
        return false;
 
@@ -2959,14 +3131,14 @@ parse_directive (pass_sprintf_length::call_info &info,
         the next field is the optional flags followed by an optional
         width.  */
       for ( ; ; ) {
-       switch (*pf)
+       switch (target_to_host (*pf))
          {
          case ' ':
          case '0':
          case '+':
          case '-':
          case '#':
-           dir.set_flag (*pf++);
+           dir.set_flag (target_to_host (*pf++));
            break;
 
          default:
@@ -2975,13 +3147,13 @@ parse_directive (pass_sprintf_length::call_info &info,
       }
 
     start_width:
-      if (ISDIGIT (*pf))
+      if (ISDIGIT (target_to_host (*pf)))
        {
-         char *end;
-         width = strtol (pf, &end, 10);
-         pf = end;
+         werange = 0;
+         pwidth = pf;
+         width = target_strtol10 (&pf, &werange);
        }
-      else if ('*' == *pf)
+      else if (target_to_host (*pf) == '*')
        {
          if (*argno < gimple_call_num_args (info.callstmt))
            star_width = gimple_call_arg (info.callstmt, (*argno)++);
@@ -2993,7 +3165,7 @@ parse_directive (pass_sprintf_length::call_info &info,
            }
          ++pf;
        }
-      else if ('\'' == *pf)
+      else if (target_to_host (*pf) == '\'')
        {
          /* The POSIX apostrophe indicating a numeric grouping
             in the current locale.  Even though it's possible to
@@ -3005,17 +3177,16 @@ parse_directive (pass_sprintf_length::call_info &info,
     }
 
  start_precision:
-  if ('.' == *pf)
+  if (target_to_host (*pf) == '.')
     {
       ++pf;
 
-      if (ISDIGIT (*pf))
+      if (ISDIGIT (target_to_host (*pf)))
        {
-         char *end;
-         precision = strtol (pf, &end, 10);
-         pf = end;
+         pprec = pf;
+         precision = target_strtol10 (&pf, &perange);
        }
-      else if ('*' == *pf)
+      else if (target_to_host (*pf) == '*')
        {
          if (*argno < gimple_call_num_args (info.callstmt))
            star_precision = gimple_call_arg (info.callstmt, (*argno)++);
@@ -3035,10 +3206,10 @@ parse_directive (pass_sprintf_length::call_info &info,
        }
     }
 
-  switch (*pf)
+  switch (target_to_host (*pf))
     {
     case 'h':
-      if (pf[1] == 'h')
+      if (target_to_host (pf[1]) == 'h')
        {
          ++pf;
          dir.modifier = FMT_LEN_hh;
@@ -3059,7 +3230,7 @@ parse_directive (pass_sprintf_length::call_info &info,
       break;
 
     case 'l':
-      if (pf[1] == 'l')
+      if (target_to_host (pf[1]) == 'l')
        {
          ++pf;
          dir.modifier = FMT_LEN_ll;
@@ -3080,7 +3251,7 @@ parse_directive (pass_sprintf_length::call_info &info,
       break;
     }
 
-  switch (*pf)
+  switch (target_to_host (*pf))
     {
       /* Handle a sole '%' character the same as "%%" but since it's
         undefined prevent the result from being folded.  */
@@ -3141,7 +3312,14 @@ parse_directive (pass_sprintf_length::call_info &info,
       return 0;
     }
 
-  dir.specifier = *pf++;
+  dir.specifier = target_to_host (*pf++);
+
+  /* Store the length of the format directive.  */
+  dir.len = pf - pcnt;
+
+  /* Buffer for the directive in the host character set (used when
+     the source character set is different).  */
+  char hostdir[32];
 
   if (star_width)
     {
@@ -3156,7 +3334,25 @@ parse_directive (pass_sprintf_length::call_info &info,
        }
     }
   else
-    dir.set_width (width);
+    {
+      if (width == LONG_MAX && werange)
+       {
+         size_t begin = dir.beg - info.fmtstr + (pwidth - pcnt);
+         size_t caret = begin + (werange - pcnt);
+         size_t end = pf - info.fmtstr - 1;
+
+         /* Create a location for the width part of the directive,
+            pointing the caret at the first out-of-range digit.  */
+         substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+                               caret, begin, end);
+
+         fmtwarn (dirloc, NULL, NULL,
+                  info.warnopt (), "%<%.*s%> directive width out of range",
+                  dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+       }
+
+      dir.set_width (width);
+    }
 
   if (star_precision)
     {
@@ -3171,7 +3367,26 @@ parse_directive (pass_sprintf_length::call_info &info,
        }
     }
   else
-    dir.set_precision (precision);
+    {
+      if (precision == LONG_MAX && perange)
+       {
+         size_t begin = dir.beg - info.fmtstr + (pprec - pcnt) - 1;
+         size_t caret = dir.beg - info.fmtstr + (perange - pcnt) - 1;
+         size_t end = pf - info.fmtstr - 2;
+
+         /* Create a location for the precision part of the directive,
+            including the leading period, pointing the caret at the first
+            out-of-range digit .  */
+         substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
+                               caret, begin, end);
+
+         fmtwarn (dirloc, NULL, NULL,
+                  info.warnopt (), "%<%.*s%> directive precision out of range",
+                  dir.len, target_to_host (hostdir, sizeof hostdir, dir.beg));
+       }
+
+      dir.set_precision (precision);
+    }
 
   /* Extract the argument if the directive takes one and if it's
      available (e.g., the function doesn't take a va_list).  Treat
@@ -3181,9 +3396,6 @@ parse_directive (pass_sprintf_length::call_info &info,
       && *argno < gimple_call_num_args (info.callstmt))
     dir.arg = gimple_call_arg (info.callstmt, dollar ? dollar : (*argno)++);
 
-  /* Return the length of the format directive.  */
-  dir.len = pf - pcnt;
-
   if (dump_file)
     {
       fprintf (dump_file, "  Directive %u at offset %llu: \"%.*s\"",
@@ -3708,6 +3920,8 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 unsigned int
 pass_sprintf_length::execute (function *fun)
 {
+  init_target_to_host_charmap ();
+
   basic_block bb;
   FOR_EACH_BB_FN (bb, fun)
     {
index 3e1b9959c7018cdc4a93145b9ead53a21f9971cc..3334d6d788889a15d99a8e13bac068f43de119a4 100644 (file)
@@ -1,3 +1,8 @@
+2017-04-28  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/80523
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-18.c: New test.
+
 2017-04-28  Tom de Vries  <tom@codesourcery.com>
 
        PR testsuite/80557
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-18.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-18.c
new file mode 100644 (file)
index 0000000..55c3974
--- /dev/null
@@ -0,0 +1,141 @@
+/* PR tree-optimization/80523 - -Wformat-overflow doesn't consider
+   -fexec-charset
+   { dg-do compile }
+   { dg-require-iconv "IBM1047" }
+   { dg-options "-O2 -Wall -Wno-format -Wformat-overflow -fexec-charset=IBM1047 -ftrack-macro-expansion=0" } */
+
+char buf[1];
+void sink (void*);
+
+#define T(...) (__builtin_sprintf (buf + 1, __VA_ARGS__), sink (buf))
+
+/* Exercise all special C and POSIX characters.  */
+
+void test_characters ()
+{
+  T ("%%");           /* { dg-warning ".%%. directive writing 1 byte" } */
+
+  T ("%A",    0.0);   /* { dg-warning ".%A. directive writing between 6 and 20 " } */
+  T ("%a",    0.0);   /* { dg-warning ".%a. directive writing between 6 and 20 " } */
+
+  T ("%C",    'a');   /* { dg-warning ".%C. directive writing 1 byte" "bug 80537" { xfail *-*-* } } */
+  T ("%c",    'a');   /* { dg-warning ".%c. directive writing 1 byte" } */
+
+  T ("%d",     12);   /* { dg-warning ".%d. directive writing 2 bytes" } */
+  T ("% d",    12);   /* { dg-warning ".% d. directive writing 3 bytes" } */
+  T ("%-d",   123);   /* { dg-warning ".%-d. directive writing 3 bytes" } */
+  T ("%+d",  1234);   /* { dg-warning ".%\\+d. directive writing 5 bytes" } */
+  T ("%'d",  1234);   /* { dg-warning ".%'d. directive writing 5 bytes" "bug 80535" { xfail *-*-* } } */
+  T ("%1$d", 2345);   /* { dg-warning ".%1\\\$d. directive writing 4 bytes" } */
+
+  /* Verify that digits are correctly interpreted as width and precision.  */
+  T ("%0d", 12345);   /* { dg-warning ".%0d. directive writing 5 bytes" } */
+  T ("%1d", 12345);   /* { dg-warning ".%1d. directive writing 5 bytes" } */
+  T ("%2d", 12345);   /* { dg-warning ".%2d. directive writing 5 bytes" } */
+  T ("%3d", 12345);   /* { dg-warning ".%3d. directive writing 5 bytes" } */
+  T ("%4d", 12345);   /* { dg-warning ".%4d. directive writing 5 bytes" } */
+  T ("%5d", 12345);   /* { dg-warning ".%5d. directive writing 5 bytes" } */
+  T ("%6d", 12345);   /* { dg-warning ".%6d. directive writing 6 bytes" } */
+  T ("%7d", 12345);   /* { dg-warning ".%7d. directive writing 7 bytes" } */
+  T ("%8d", 12345);   /* { dg-warning ".%8d. directive writing 8 bytes" } */
+  T ("%9d", 12345);   /* { dg-warning ".%9d. directive writing 9 bytes" } */
+
+  T ("%.0d", 12345);  /* { dg-warning ".%.0d. directive writing 5 bytes" } */
+  T ("%.1d", 12345);  /* { dg-warning ".%.1d. directive writing 5 bytes" } */
+  T ("%.2d", 12345);  /* { dg-warning ".%.2d. directive writing 5 bytes" } */
+  T ("%.3d", 12345);  /* { dg-warning ".%.3d. directive writing 5 bytes" } */
+  T ("%.4d", 12345);  /* { dg-warning ".%.4d. directive writing 5 bytes" } */
+  T ("%.5d", 12345);  /* { dg-warning ".%.5d. directive writing 5 bytes" } */
+  T ("%.6d", 12345);  /* { dg-warning ".%.6d. directive writing 6 bytes" } */
+  T ("%.7d", 12345);  /* { dg-warning ".%.7d. directive writing 7 bytes" } */
+  T ("%.8d", 12345);  /* { dg-warning ".%.8d. directive writing 8 bytes" } */
+  T ("%.9d", 12345);  /* { dg-warning ".%.9d. directive writing 9 bytes" } */
+
+  T ("%hhd",    12);   /* { dg-warning ".%hhd. directive writing 2 bytes" } */
+  T ("%hd",    234);   /* { dg-warning ".%hd. directive writing 3 bytes" } */
+
+  {
+    const __PTRDIFF_TYPE__ i = 3456;
+    T ("%jd",   i);  /* { dg-warning ".%jd. directive writing 4 bytes" } */
+  }
+
+  T ("%ld",  45678L);  /* { dg-warning ".%ld. directive writing 5 bytes" } */
+
+  {
+    const __PTRDIFF_TYPE__ i = 56789;
+    T ("%td",   i);  /* { dg-warning ".%td. directive writing 5 bytes" } */
+  }
+
+  {
+    const __SIZE_TYPE__ i = 67890;
+    T ("%zd",   i);  /* { dg-warning ".%zd. directive writing 5 bytes" } */
+  }
+
+  T ("%E",    0.0);   /* { dg-warning ".%E. directive writing 12 bytes" } */
+  T ("%e",    0.0);   /* { dg-warning ".%e. directive writing 12 bytes" } */
+  T ("%F",    0.0);   /* { dg-warning ".%F. directive writing 8 bytes" } */
+  T ("%f",    0.0);   /* { dg-warning ".%f. directive writing 8 bytes" } */
+  T ("%G",    0.0);   /* { dg-warning ".%G. directive writing 1 byte" } */
+  T ("%g",    0.0);   /* { dg-warning ".%g. directive writing 1 byte" } */
+
+  T ("%i",     123);  /* { dg-warning ".%i. directive writing 3 bytes" } */
+
+  {
+    int n;
+
+    T ("%n",    &n);  /* { dg-warning "writing a terminating nul" } */
+    T ("%nH",   &n);  /* { dg-warning ".H. directive writing 1 byte" } */
+  }
+
+  T ("%o",     999);  /* { dg-warning ".%o. directive writing 4 bytes" } */
+  T ("%#o",    999);  /* { dg-warning ".%#o. directive writing 5 bytes" } */
+
+  T ("%x",    1234);  /* { dg-warning ".%x. directive writing 3 bytes" } */
+  T ("%#X",   1235);  /* { dg-warning ".%#X. directive writing 5 bytes" } */
+
+  T ("%S",    L"1");  /* { dg-warning ".%S. directive writing 1 byte" } */
+  T ("%-s",    "1");  /* { dg-warning ".%-s. directive writing 1 byte" } */
+
+  /* Verify that characters in the source character set appear in
+     the text of the warning unchanged (i.e., not as their equivalents
+     in the execution character set on the target).  The trailing %%
+     disables sprintf->strcpy optimization.  */
+  T ("ABCDEFGHIJ%%");   /* { dg-warning ".ABCDEFGHIJ. directive writing 10 bytes" } */
+  T ("KLMNOPQRST%%");   /* { dg-warning ".KLMNOPQRST. directive writing 10 bytes" } */
+  T ("UVWXYZ%%");       /* { dg-warning ".UVWXYZ. directive writing 6 bytes" } */
+
+  T ("abcdefghij%%");   /* { dg-warning ".abcdefghij. directive writing 10 bytes" } */
+  T ("klmnopqrst%%");   /* { dg-warning ".klmnopqrst. directive writing 10 bytes" } */
+  T ("uvwxyz%%");       /* { dg-warning ".uvwxyz. directive writing 6 bytes" } */
+}
+
+#undef T
+#define T(...) (__builtin_sprintf (d, __VA_ARGS__), sink (d))
+
+void test_width_and_precision_out_of_range (char *d)
+{
+#if __LONG_MAX__ == 2147483647
+#  define   MAX_P1_STR "2147483648"
+#elif __LONG_MAX__ == 9223372036854775807
+#  define MAX_P1_STR "9223372036854775808"
+#endif
+
+  T ("%" MAX_P1_STR "i", 0);    /* { dg-warning "width out of range" } */
+  /* { dg-warning "result to exceed .INT_MAX. " "" { target *-*-* } .-1 } */
+  T ("%." MAX_P1_STR "i", 0);   /* { dg-warning "precision out of range" } */
+
+  /* The following is diagnosed by -Wformat (disabled here).  */
+  /* T ("%" MAX_P1_STR "$i", 0); */
+}
+
+/* Verify that an excessively long directive is truncated and the truncation
+   is indicated by three trailing dots in the text of the warning.  */
+
+void test_overlong_plain_string ()
+{
+  static const char longfmtstr[] =
+    "0123456789012345678901234567890123456789012345678901234567890123456789%%";
+
+  char d[1];
+  T (longfmtstr);   /* { dg-warning ".0123\[0-9\]\*\.\.\.. directive writing 70 bytes" } */
+}