PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length...
authorMartin Sebor <msebor@redhat.com>
Mon, 28 Nov 2016 21:41:41 +0000 (21:41 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Mon, 28 Nov 2016 21:41:41 +0000 (14:41 -0700)
PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length warning with non-constant width or precision
PR middle-end/78520 - missing warning for snprintf with size greater than INT_MAX

gcc/ChangeLog:

PR middle-end/78520
* gimple-ssa-sprintf.c (target_max_value): Remove.
(target_int_max, target_size_max): Use TYPE_MAX_VALUE.
(get_width_and_precision): New function.
(format_integer, format_floating, get_string_length, format_string):
Correct handling of width and precision with unknown value.
(format_directive): Add warning.
(pass_sprintf_length::compute_format_length): Allow for precision
to consist of a sole period with no asterisk or digits after it.

gcc/testsuite/ChangeLog:

PR middle-end/78520
* gcc.dg/tree-ssa/builtin-sprintf-5.c: Add test cases.
* gcc.dg/tree-ssa/builtin-sprintf-6.c: New test.
* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Add test cases.
* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Add test cases.

From-SVN: r242935

gcc/ChangeLog
gcc/gimple-ssa-sprintf.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c

index ab62746..12a717e 100644 (file)
@@ -1,3 +1,15 @@
+2016-11-28  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/78520
+       * gimple-ssa-sprintf.c (target_max_value): Remove.
+       (target_int_max, target_size_max): Use TYPE_MAX_VALUE.
+       (get_width_and_precision): New function.
+       (format_integer, format_floating, get_string_length, format_string):
+       Correct handling of width and precision with unknown value.
+       (format_directive): Add warning.
+       (pass_sprintf_length::compute_format_length): Allow for precision
+       to consist of a sole period with no asterisk or digits after it.
+
 2016-11-28  Jakub Jelinek  <jakub@redhat.com>
 
        PR rtl-optimization/78546
index 71014eb..43bc560 100644 (file)
@@ -226,24 +226,10 @@ struct format_result
 
 /* Return the value of INT_MIN for the target.  */
 
-static HOST_WIDE_INT
+static inline HOST_WIDE_INT
 target_int_min ()
 {
-  const unsigned HOST_WIDE_INT int_min
-    = HOST_WIDE_INT_M1U << (TYPE_PRECISION (integer_type_node) - 1);
-
-  return int_min;
-}
-
-/* Return the largest value for TYPE on the target.  */
-
-static unsigned HOST_WIDE_INT
-target_max_value (tree type)
-{
-  const unsigned HOST_WIDE_INT max_value
-    = HOST_WIDE_INT_M1U >> (HOST_BITS_PER_WIDE_INT
-                           - TYPE_PRECISION (type) + 1);
-  return max_value;
+  return tree_to_shwi (TYPE_MIN_VALUE (integer_type_node));
 }
 
 /* Return the value of INT_MAX for the target.  */
@@ -251,7 +237,7 @@ target_max_value (tree type)
 static inline unsigned HOST_WIDE_INT
 target_int_max ()
 {
-  return target_max_value (integer_type_node);
+  return tree_to_uhwi (TYPE_MAX_VALUE (integer_type_node));
 }
 
 /* Return the value of SIZE_MAX for the target.  */
@@ -259,7 +245,7 @@ target_int_max ()
 static inline unsigned HOST_WIDE_INT
 target_size_max ()
 {
-  return target_max_value (size_type_node);
+  return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
 }
 
 /* Return the constant initial value of DECL if available or DECL
@@ -845,6 +831,43 @@ format_pointer (const conversion_spec &spec, tree arg)
   return res;
 }
 
+/* Set *PWIDTH and *PPREC according to the width and precision specified
+   in SPEC.  Each is set to HOST_WIDE_INT_MIN when the corresponding
+   field is specified but unknown, to zero for width and -1 for precision,
+   respectively when it's not specified, or to a non-negative value
+   corresponding to the known value.  */
+static void
+get_width_and_precision (const conversion_spec &spec,
+                        HOST_WIDE_INT *pwidth, HOST_WIDE_INT *pprec)
+{
+  HOST_WIDE_INT width = spec.have_width ? spec.width : 0;
+  HOST_WIDE_INT prec = spec.have_precision ? spec.precision : -1;
+
+  if (spec.star_width)
+    {
+      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+       width = abs (tree_to_shwi (spec.star_width));
+      else
+       width = HOST_WIDE_INT_MIN;
+    }
+
+  if (spec.star_precision)
+    {
+      if (TREE_CODE (spec.star_precision) == INTEGER_CST)
+       {
+         prec = tree_to_shwi (spec.star_precision);
+         if (prec < 0)
+           prec = 0;
+       }
+      else
+       prec = HOST_WIDE_INT_MIN;
+    }
+
+  *pwidth = width;
+  *pprec = prec;
+}
+
+
 /* Return a range representing the minimum and maximum number of bytes
    that the conversion specification SPEC will write on output for the
    integer argument ARG when non-null.  ARG may be null (for vararg
@@ -855,18 +878,11 @@ format_integer (const conversion_spec &spec, tree arg)
 {
   tree intmax_type_node;
   tree uintmax_type_node;
-  /* Set WIDTH and PRECISION to either the values in the format
-     specification or to zero.  */
-  int width = spec.have_width ? spec.width : 0;
-  int prec = spec.have_precision ? spec.precision : 0;
 
-  if (spec.star_width)
-    width = (TREE_CODE (spec.star_width) == INTEGER_CST
-            ? tree_to_shwi (spec.star_width) : 0);
-
-  if (spec.star_precision)
-    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
-           ? tree_to_shwi (spec.star_precision) : 0);
+  /* Set WIDTH and PRECISION based on the specification.  */
+  HOST_WIDE_INT width;
+  HOST_WIDE_INT prec;
+  get_width_and_precision (spec, &width, &prec);
 
   bool sign = spec.specifier == 'd' || spec.specifier == 'i';
 
@@ -936,15 +952,8 @@ format_integer (const conversion_spec &spec, tree arg)
     }
   else if (TREE_CODE (arg) == INTEGER_CST)
     {
-      /* The minimum and maximum number of bytes produced by
-        the directive.  */
-      fmtresult res;
-
       /* When a constant argument has been provided use its value
         rather than type to determine the length of the output.  */
-      res.bounded = true;
-      res.constant = true;
-      res.knownrange = true;
 
       /* Base to format the number in.  */
       int base;
@@ -977,25 +986,56 @@ format_integer (const conversion_spec &spec, tree arg)
          gcc_unreachable ();
        }
 
-      /* Convert the argument to the type of the directive.  */
-      arg = fold_convert (dirtype, arg);
+      int len;
+
+      if ((prec == HOST_WIDE_INT_MIN || prec == 0) && integer_zerop (arg))
+       {
+         /* As a special case, a precision of zero with an argument
+            of zero results in zero bytes regardless of flags (with
+            width having the normal effect).  This must extend to
+            the case of a specified precision with an unknown value
+            because it can be zero.  */
+         len = 0;
+       }
+      else
+       {
+         /* Convert the argument to the type of the directive.  */
+         arg = fold_convert (dirtype, arg);
 
-      maybesign |= spec.get_flag ('+');
+         maybesign |= spec.get_flag ('+');
 
-      /* True when a conversion is preceded by a prefix indicating the base
-        of the argument (octal or hexadecimal).  */
-      bool maybebase = spec.get_flag ('#');
-      int len = tree_digits (arg, base, maybesign, maybebase);
+         /* True when a conversion is preceded by a prefix indicating the base
+            of the argument (octal or hexadecimal).  */
+         bool maybebase = spec.get_flag ('#');
+         len = tree_digits (arg, base, maybesign, maybebase);
 
-      if (len < prec)
-       len = prec;
+         if (len < prec)
+           len = prec;
+       }
 
       if (len < width)
        len = width;
 
-      res.range.max = len;
-      res.range.min = res.range.max;
-      res.bounded = true;
+      /* The minimum and maximum number of bytes produced by the directive.  */
+      fmtresult res;
+
+      res.range.min = len;
+
+      /* The upper bound of the number of bytes is unlimited when either
+        width or precision is specified but its value is unknown, and
+        the same as the lower bound otherwise.  */
+      if (width == HOST_WIDE_INT_MIN || prec == HOST_WIDE_INT_MIN)
+       {
+         res.range.max = HOST_WIDE_INT_MAX;
+       }
+      else
+       {
+         res.range.max = len;
+         res.bounded = true;
+         res.constant = true;
+         res.knownrange = true;
+         res.bounded = true;
+       }
 
       return res;
     }
@@ -1106,8 +1146,10 @@ format_integer (const conversion_spec &spec, tree arg)
         or one whose value range cannot be determined, create a T_MIN
         constant if the argument's type is signed and T_MAX otherwise,
         and use those to compute the range of bytes that the directive
-        can output.  */
-      argmin = build_int_cst (argtype, 1);
+        can output.  When precision is specified but unknown, use zero
+        as the minimum since it results in no bytes on output (unless
+        width is specified to be greater than 0).  */
+      argmin = build_int_cst (argtype, prec != HOST_WIDE_INT_MIN);
 
       int typeprec = TYPE_PRECISION (dirtype);
       int argprec = TYPE_PRECISION (argtype);
@@ -1257,11 +1299,13 @@ format_floating (const conversion_spec &spec, int width, int prec)
       {
        /* The minimum output is "0x.p+0".  */
        res.range.min = 6 + (prec > 0 ? prec : 0);
-       res.range.max = format_floating_max (type, 'a', prec);
+       res.range.max = (width == INT_MIN
+                        ? HOST_WIDE_INT_MAX
+                        : format_floating_max (type, 'a', prec));
 
        /* The output of "%a" is fully specified only when precision
-          is explicitly specified.  */
-       res.bounded = -1 < prec;
+          is explicitly specified and width isn't unknown.  */
+       res.bounded = INT_MIN != width && -1 < prec;
        break;
       }
 
@@ -1274,13 +1318,16 @@ format_floating (const conversion_spec &spec, int width, int prec)
        res.range.min = (sign
                         + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
                         + 2 /* e+ */ + 2);
-       /* The maximum output is the minimum plus sign (unless already
-          included), plus the difference between the minimum exponent
-          of 2 and the maximum exponent for the type.  */
-       res.range.max = res.range.min + !sign + logexpdigs - 2;
-
-       /* "%e" is fully specified and the range of bytes is bounded.  */
-       res.bounded = true;
+       /* Unless width is uknown the maximum output is the minimum plus
+          sign (unless already included), plus the difference between
+          the minimum exponent of 2 and the maximum exponent for the type.  */
+       res.range.max = (width == INT_MIN
+                        ? HOST_WIDE_INT_M1U
+                        : res.range.min + !sign + logexpdigs - 2);
+
+       /* "%e" is fully specified and the range of bytes is bounded
+          unless width is unknown.  */
+       res.bounded = INT_MIN != width;
        break;
       }
 
@@ -1296,10 +1343,11 @@ format_floating (const conversion_spec &spec, int width, int prec)
          format_floating_max (double_type_node, 'f'),
          format_floating_max (long_double_type_node, 'f')
        };
-       res.range.max = f_max [ldbl];
+       res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : f_max [ldbl];
 
-       /* "%f" is fully specified and the range of bytes is bounded.  */
-       res.bounded = true;
+       /* "%f" is fully specified and the range of bytes is bounded
+          unless width is unknown.  */
+       res.bounded = INT_MIN != width;
        break;
       }
     case 'G':
@@ -1313,10 +1361,11 @@ format_floating (const conversion_spec &spec, int width, int prec)
          format_floating_max (double_type_node, 'g'),
          format_floating_max (long_double_type_node, 'g')
        };
-       res.range.max = g_max [ldbl];
+       res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : g_max [ldbl];
 
-       /* "%g" is fully specified and the range of bytes is bounded.  */
-       res.bounded = true;
+       /* "%g" is fully specified and the range of bytes is bounded
+          unless width is unknown.  */
+       res.bounded = INT_MIN != width;
        break;
       }
 
@@ -1342,6 +1391,9 @@ format_floating (const conversion_spec &spec, int width, int prec)
 static fmtresult
 format_floating (const conversion_spec &spec, tree arg)
 {
+  /* Set WIDTH to -1 when it's not specified, to INT_MIN when it is
+     specified by the asterisk to an unknown value, and otherwise to
+     a non-negative value corresponding to the specified width.  */
   int width = -1;
   int prec = -1;
 
@@ -1354,12 +1406,13 @@ format_floating (const conversion_spec &spec, tree arg)
   else if (spec.star_width)
     {
       if (TREE_CODE (spec.star_width) == INTEGER_CST)
-       width = tree_to_shwi (spec.star_width);
-      else
        {
-         res.range.min = res.range.max = HOST_WIDE_INT_M1U;
-         return res;
+         width = tree_to_shwi (spec.star_width);
+         if (width < 0)
+           width = -width;
        }
+      else
+       width = INT_MIN;
     }
 
   if (spec.have_precision)
@@ -1370,6 +1423,7 @@ format_floating (const conversion_spec &spec, tree arg)
        prec = tree_to_shwi (spec.star_precision);
       else
        {
+         /* FIXME: Handle non-constant precision.  */
          res.range.min = res.range.max = HOST_WIDE_INT_M1U;
          return res;
        }
@@ -1409,9 +1463,9 @@ format_floating (const conversion_spec &spec, tree arg)
          *pfmt++ = *pf;
 
       /* Append width when specified and precision.  */
-      if (width != -1)
+      if (-1 < width)
        pfmt += sprintf (pfmt, "%i", width);
-      if (prec != -1)
+      if (-1 < prec)
        pfmt += sprintf (pfmt, ".%i", prec);
 
       /* Append the MPFR 'R' floating type specifier (no length modifier
@@ -1438,16 +1492,24 @@ format_floating (const conversion_spec &spec, tree arg)
          *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval);
        }
 
+      /* The range of output is known even if the result isn't bounded.  */
+      if (width == INT_MIN)
+       {
+         res.knownrange = false;
+         res.range.max = HOST_WIDE_INT_MAX;
+       }
+      else
+       res.knownrange = true;
+
       /* The output of all directives except "%a" is fully specified
         and so the result is bounded unless it exceeds INT_MAX.
         For "%a" the output is fully specified only when precision
         is explicitly specified.  */
-      res.bounded = ((TOUPPER (spec.specifier) != 'A'
-                     || (0 <= prec && (unsigned) prec < target_int_max ()))
+      res.bounded = (res.knownrange
+                    && (TOUPPER (spec.specifier) != 'A'
+                        || (0 <= prec && (unsigned) prec < target_int_max ()))
                     && res.range.min < target_int_max ());
 
-      /* The range of output is known even if the result isn't bounded.  */
-      res.knownrange = true;
       return res;
     }
 
@@ -1517,20 +1579,10 @@ get_string_length (tree str)
 static fmtresult
 format_string (const conversion_spec &spec, tree arg)
 {
-  unsigned width = spec.have_width && spec.width > 0 ? spec.width : 0;
-  int prec = spec.have_precision ? spec.precision : -1;
-
-  if (spec.star_width)
-    {
-      width = (TREE_CODE (spec.star_width) == INTEGER_CST
-              ? tree_to_shwi (spec.star_width) : 0);
-      if (width > INT_MAX)
-       width = 0;
-    }
-
-  if (spec.star_precision)
-    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
-           ? tree_to_shwi (spec.star_precision) : -1);
+  /* Set WIDTH and PRECISION based on the specification.  */
+  HOST_WIDE_INT width;
+  HOST_WIDE_INT prec;
+  get_width_and_precision (spec, &width, &prec);
 
   fmtresult res;
 
@@ -1590,11 +1642,12 @@ format_string (const conversion_spec &spec, tree arg)
          res.range = slen.range;
 
          /* The output of "%s" and "%ls" directives with a constant
-            string is in a known range.  For "%s" it is the length
-            of the string.  For "%ls" it is in the range [length,
-            length * MB_LEN_MAX].  (The final range can be further
-            constrained by width and precision but it's always known.)  */
-         res.knownrange = true;
+            string is in a known range unless width of an unknown value
+            is specified.  For "%s" it is the length of the string.  For
+            "%ls" it is in the range [length, length * MB_LEN_MAX].
+            (The final range can be further constrained by width and
+            precision but it's always known.)  */
+         res.knownrange = -1 < width;
 
          if (spec.modifier == FMT_LEN_l)
            {
@@ -1622,19 +1675,32 @@ format_string (const conversion_spec &spec, tree arg)
              if (0 <= prec)
                res.range.max = prec;
            }
-         else
+         else if (0 <= width)
            {
-             /* The output od a "%s" directive with a constant argument
-                is bounded, constant, and obviously in a known range.  */
+             /* The output of a "%s" directive with a constant argument
+                and constant or no width is bounded.  It is constant if
+                precision is either not specified or it is specified and
+                its value is known.  */
              res.bounded = true;
-             res.constant = true;
+             res.constant = prec != HOST_WIDE_INT_MIN;
+           }
+         else if (width == HOST_WIDE_INT_MIN)
+           {
+             /* Specified but unknown width makes the output unbounded.  */
+             res.range.max = HOST_WIDE_INT_MAX;
            }
 
-         if (0 <= prec && (unsigned)prec < res.range.min)
+         if (0 <= prec && (unsigned HOST_WIDE_INT)prec < res.range.min)
            {
              res.range.min = prec;
              res.range.max = prec;
            }
+         else if (prec == HOST_WIDE_INT_MIN)
+           {
+             /* When precision is specified but not known the lower
+                bound is assumed to be as low as zero.  */
+             res.range.min = 0;
+           }
        }
       else
        {
@@ -1648,10 +1714,10 @@ format_string (const conversion_spec &spec, tree arg)
            {
              if (slen.range.min >= target_int_max ())
                slen.range.min = 0;
-             else if ((unsigned)prec < slen.range.min)
+             else if ((unsigned HOST_WIDE_INT)prec < slen.range.min)
                slen.range.min = prec;
 
-             if ((unsigned)prec < slen.range.max
+             if ((unsigned HOST_WIDE_INT)prec < slen.range.max
                  || slen.range.max >= target_int_max ())
                slen.range.max = prec;
            }
@@ -1674,20 +1740,23 @@ format_string (const conversion_spec &spec, tree arg)
     }
 
   /* Adjust the lengths for field width.  */
-  if (res.range.min < width)
-    res.range.min = width;
+  if (0 < width)
+    {
+      if (res.range.min < (unsigned HOST_WIDE_INT)width)
+       res.range.min = width;
 
-  if (res.range.max < width)
-    res.range.max = width;
+      if (res.range.max < (unsigned HOST_WIDE_INT)width)
+       res.range.max = width;
 
-  /* Adjust BOUNDED if width happens to make them equal.  */
-  if (res.range.min == res.range.max && res.range.min < target_int_max ()
-      && bounded)
-    res.bounded = true;
+      /* Adjust BOUNDED if width happens to make them equal.  */
+      if (res.range.min == res.range.max && res.range.min < target_int_max ()
+         && bounded)
+       res.bounded = true;
+    }
 
   /* When precision is specified the range of characters on output
      is known to be bounded by it.  */
-  if (-1 < prec)
+  if (-1 < width && -1 < prec)
     res.knownrange = true;
 
   return res;
@@ -1803,7 +1872,7 @@ format_directive (const pass_sprintf_length::call_info &info,
                                    (int)cvtlen, cvtbeg, fmtres.range.min,
                                    navail);
                }
-             else
+             else if (fmtres.range.max < HOST_WIDE_INT_MAX)
                {
                  const char* fmtstr
                    = (info.bounded
@@ -1817,6 +1886,19 @@ format_directive (const pass_sprintf_length::call_info &info,
                                    (int)cvtlen, cvtbeg,
                                    fmtres.range.min, fmtres.range.max, navail);
                }
+             else
+               {
+                 const char* fmtstr
+                   = (info.bounded
+                      ? G_("%<%.*s%> directive output truncated writing "
+                           "%wu or more bytes into a region of size %wu")
+                      : G_("%<%.*s%> directive writing %wu or more bytes "
+                           "into a region of size %wu"));
+                 warned = fmtwarn (dirloc, pargrange, NULL,
+                                   OPT_Wformat_length_, fmtstr,
+                                   (int)cvtlen, cvtbeg,
+                                   fmtres.range.min, navail);
+               }
            }
          else if (navail < fmtres.range.max
                   && (((spec.specifier == 's'
@@ -2273,13 +2355,22 @@ pass_sprintf_length::compute_format_length (const call_info &info,
 
       if (dollar || !spec.star_width)
        {
-         if (spec.have_width && spec.width == 0)
+         if (spec.have_width)
            {
-             /* The '0' that has been interpreted as a width above is
-                actually a flag.  Reset HAVE_WIDTH, set the '0' flag,
-                and continue processing other flags.  */
-             spec.have_width = false;
-             spec.set_flag ('0');
+             if (spec.width == 0)
+               {
+                 /* The '0' that has been interpreted as a width above is
+                    actually a flag.  Reset HAVE_WIDTH, set the '0' flag,
+                    and continue processing other flags.  */
+                 spec.have_width = false;
+                 spec.set_flag ('0');
+               }
+             else if (!dollar)
+               {
+                 /* (Non-zero) width has been seen.  The next character
+                    is either a period or a digit.  */
+                 goto start_precision;
+               }
            }
          /* When either '$' has been seen, or width has not been seen,
             the next field is the optional flags followed by an optional
@@ -2324,6 +2415,7 @@ pass_sprintf_length::compute_format_length (const call_info &info,
            }
        }
 
+    start_precision:
       if ('.' == *pf)
        {
          ++pf;
@@ -2341,7 +2433,12 @@ pass_sprintf_length::compute_format_length (const call_info &info,
              ++pf;
            }
          else
-           return;
+           {
+             /* The decimal precision or the asterisk are optional.
+                When neither is specified it's taken to be zero.  */
+             spec.precision = 0;
+             spec.have_precision = true;
+           }
        }
 
       switch (*pf)
@@ -2701,9 +2798,9 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
 
   if (idx_dstsize == HOST_WIDE_INT_M1U)
     {
-      // For non-bounded functions like sprintf, to determine
-      // the size of the destination from the object or pointer
-      // passed to it as the first argument.
+      /* For non-bounded functions like sprintf, determine the size
+        of the destination from the object or pointer passed to it
+        as the first argument.  */
       dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0));
     }
   else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize))
@@ -2715,10 +2812,18 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
          dstsize = tree_to_uhwi (size);
          /* No object can be larger than SIZE_MAX bytes (half the address
             space) on the target.  This imposes a limit that's one byte
-            less than that.  */
+            less than that.
+            The functions are defined only for output of at most INT_MAX
+            bytes.  Specifying a bound in excess of that limit effectively
+            defeats the bounds checking (and on some implementations such
+            as Solaris cause the function to fail with EINVAL).  */
          if (dstsize >= target_size_max () / 2)
            warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
-                       "specified destination size %wu too large",
+                       "specified destination size %wu is too large",
+                       dstsize);
+         else if (dstsize > target_int_max ())
+           warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+                       "specified destination size %wu exceeds %<INT_MAX %>",
                        dstsize);
        }
       else if (TREE_CODE (size) == SSA_NAME)
index 0579927..e4dff3c 100644 (file)
@@ -1,3 +1,11 @@
+2016-11-28  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/78520
+       * gcc.dg/tree-ssa/builtin-sprintf-5.c: Add test cases.
+       * gcc.dg/tree-ssa/builtin-sprintf-6.c: New test.
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Add test cases.
+       * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Add test cases.
+
 2016-11-28  Jakub Jelinek  <jakub@redhat.com>
 
        PR c++/72808
index d568f9c..dbb0dd9 100644 (file)
@@ -44,6 +44,26 @@ void test_arg_int (int i, int n)
 
   for (i = -n; i != n; ++i)
     T (8, "%08x", i);
+
+  /*  As a special case, a precision of zero with an argument of zero
+      results in zero bytes (unless modified by width).  */
+  T (0, "%.0d", ival (0));
+  T (0, "%.0i", ival (0));
+  T (0, "%.0o", ival (0));
+  T (0, "%.0u", ival (0));
+  T (0, "%.0x", ival (0));
+
+  T (0, "%.*d", 0, ival (0));
+  T (0, "%.*i", 0, ival (0));
+  T (0, "%.*o", 0, ival (0));
+  T (0, "%.*u", 0, ival (0));
+  T (0, "%.*x", 0, ival (0));
+
+  T (1, "%1.0d", ival (0));
+  T (1, "%1.0i", ival (0));
+  T (1, "%1.0o", ival (0));
+  T (1, "%1.0u", ival (0));
+  T (1, "%1.0x", ival (0));
 }
 
 void test_arg_string (const char *s)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c
new file mode 100644 (file)
index 0000000..375fc09
--- /dev/null
@@ -0,0 +1,73 @@
+/* PR middle-end/78476 - snprintf(0, 0, ...) with known arguments not
+   optimized away
+   A negative test complementing builtin-sprintf-5.c to verify that calls
+   to the function that do not return a constant are not optimized away.
+   { dg-compile }
+   { dg-options "-O2 -fdump-tree-optimized" }
+   { dg-require-effective-target int32plus } */
+
+#define CONCAT(a, b) a ## b
+#define CAT(a, b)    CONCAT (a, b)
+
+#define T(...)                                                         \
+  do {                                                                 \
+    int CAT (n, __LINE__) = __builtin_snprintf (0, 0, __VA_ARGS__);    \
+    sink (CAT (n, __LINE__));                                          \
+  } while (0)
+
+void sink (int);
+
+static int
+int_range (int min, int max)
+{
+  extern int int_value (void);
+  int val = int_value ();
+  if (val < min || max < val)
+    val = min;
+  return val;
+}
+
+#define R(min, max) int_range (min, max)
+
+void test_arg_int (int width, int prec, int i, int n)
+{
+  T ("%i", i);
+  T ("%1i", i);
+  T ("%2i", i);
+  T ("%3i", i);
+  T ("%4i", i);
+
+  T ("%*i", width, 0);
+  T ("%*i", width, 1);
+  T ("%*i", width, i);
+
+  T ("%.*i", prec, 0);
+  T ("%.*i", prec, 1);
+  T ("%.*i", prec, i);
+  T ("%.*i", 0,    i);
+
+  T ("%i", R (1, 10));
+
+  for (i = -n; i != n; ++i)
+    T ("%*x", n, i);
+}
+
+void test_arg_string (int width, int prec, const char *s)
+{
+  T ("%-s", s);
+  T ("%1s", s);
+  T ("%.1s", s);
+  T ("%*s", width, s);
+  T ("%.*s", prec, s);
+  T ("%1.*s", prec, s);
+  T ("%*.1s", width, s);
+  T ("%*.*s", width, prec, s);
+  T ("%*s", width, "123");
+  T ("%.*s", prec, "123");
+  T ("%1.*s", prec, "123");
+  T ("%*.1s", width, "123");
+  T ("%*.*s", width, prec, "123");
+}
+
+
+/* { dg-final { scan-tree-dump-times "snprintf" 27 "optimized"} } */
index a24889b..7937149 100644 (file)
@@ -233,6 +233,8 @@ void test_sprintf_chk_s_const (void)
   T ( 1, "%*s",  1, s0);        /* { dg-warning "nul past the end" } */
   T (-1, "%*s",  1, s0);        /* No warning for unknown destination size.  */
 
+  T (1, "%.s",     "");
+  T (1, "%.s",     "123");
   T (1, "%.0s",    "123");
   T (1, "%.0s",    s3);
   T (1, "%.*s", 0, "123");
@@ -450,6 +452,24 @@ void test_sprintf_chk_hh_const (void)
   T (4, "%hhi %hhi", 10,  1);   /* { dg-warning "nul past the end" } */
   T (4, "%hhi %hhi", 11, 12);   /* { dg-warning "into a region" } */
 
+  /*  As a special case, a precision of zero with an argument of zero
+      results in zero bytes (unless modified by width).  */
+  T (1, "%.0hhd",   0);
+  T (1, "%+.0hhd",  0);
+  T (1, "%-.0hhd",  0);
+  T (1, "% .0hhd",  0);
+  T (1, "%0.0hhd",  0);         /* { dg-warning ".0. flag ignored with precision" } */
+  T (1, "%00.0hhd", 0);         /* { dg-warning "repeated .0. flag in format" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%-0.0hhd", 0);         /* { dg-warning ".0. flag ignored with .-. flag" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%.0hhi",   0);
+  T (1, "%.0hho",   0);
+  T (1, "%#.0hho",  0);
+  T (1, "%.0hhx",   0);
+  T (1, "%.0hhX",   0);
+  T (1, "%#.0hhX",  0);
+
   T (5, "%0*hhd %0*hhi", 0,  7, 0,   9);
   T (5, "%0*hhd %0*hhi", 1,  7, 1,   9);
   T (5, "%0*hhd %0*hhi", 1,  7, 2,   9);
@@ -546,14 +566,32 @@ void test_sprintf_chk_h_const (void)
   T (4, "%#hx",     0x100);     /* { dg-warning "into a region" } */
   T (4, "%#hx",        -1);     /* { dg-warning "into a region" } */
 
+  /*  As a special case, a precision of zero with an argument of zero
+      results in zero bytes (unless modified by width).  */
+  T (1, "%.0hd",        0);
+  T (1, "%+.0hd",       0);
+  T (1, "%-.0hd",       0);
+  T (1, "% .0hd",       0);
+  T (1, "%0.0hd",       0);         /* { dg-warning ".0. flag ignored with precision" } */
+  T (1, "%00.0hd",      0);         /* { dg-warning "repeated .0. flag in format" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%-0.0hd",      0);         /* { dg-warning ".0. flag ignored with .-. flag" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%.0hi",        0);
+  T (1, "%.0ho",        0);
+  T (1, "%#.0ho",       0);
+  T (1, "%.0hx",        0);
+  T (1, "%.0hX",        0);
+  T (1, "%#.0hX",       0);
+
 #undef MAX
 #define MAX   65535
 
-  T (1, "%hhu",         0);     /* { dg-warning "nul past the end" } */
-  T (1, "%hhu",         1);     /* { dg-warning "nul past the end" } */
-  T (1, "%hhu",        -1);     /* { dg-warning "into a region" } */
-  T (1, "%hhu",       MAX);     /* { dg-warning "into a region" } */
-  T (1, "%hhu",  MAX +  1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",         -1);     /* { dg-warning "into a region" } */
+  T (1, "%hu",        MAX);     /* { dg-warning "into a region" } */
+  T (1, "%hu",   MAX +  1);     /* { dg-warning "nul past the end" } */
 }
 
 /* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with
@@ -611,6 +649,24 @@ void test_sprintf_chk_integer_const (void)
   T ( 8, "%8u",         1);        /* { dg-warning "nul past the end" } */
   T ( 9, "%8u",         1);
 
+  /*  As a special case, a precision of zero with an argument of zero
+      results in zero bytes (unless modified by width).  */
+  T (1, "%.0d",         0);
+  T (1, "%+.0d",        0);
+  T (1, "%-.0d",        0);
+  T (1, "% .0d",        0);
+  T (1, "%0.0d",        0);         /* { dg-warning ".0. flag ignored with precision" } */
+  T (1, "%00.0d",       0);         /* { dg-warning "repeated .0. flag in format" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%-0.0d",       0);         /* { dg-warning ".0. flag ignored with .-. flag" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%.0i",         0);
+  T (1, "%.0o",         0);
+  T (1, "%#.0o",        0);
+  T (1, "%.0x",         0);
+  T (1, "%.0X",         0);
+  T (1, "%#.0X",        0);
+
   T ( 7, "%1$i%2$i%3$i",     1, 23, 456);
   T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456);
   T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456);   /* { dg-warning "nul past the end" } */
@@ -691,6 +747,24 @@ void test_sprintf_chk_j_const (void)
 
   T ( 8, "%8ju",     I (1));      /* { dg-warning "nul past the end" } */
   T ( 9, "%8ju",     I (1));
+
+  /*  As a special case, a precision of zero with an argument of zero
+      results in zero bytes (unless modified by width).  */
+  T (1, "%.0jd",     I (0));
+  T (1, "%+.0jd",    I (0));
+  T (1, "%-.0jd",    I (0));
+  T (1, "% .0jd",    I (0));
+  T (1, "%0.0jd",    I (0));         /* { dg-warning ".0. flag ignored with precision" } */
+  T (1, "%00.0jd",   I (0));         /* { dg-warning "repeated .0. flag in format" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%-0.0jd",   I (0));         /* { dg-warning ".0. flag ignored with .-. flag" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%.0ji",     I (0));
+  T (1, "%.0jo",     I (0));
+  T (1, "%#.0jo",    I (0));
+  T (1, "%.0jx",     I (0));
+  T (1, "%.0jX",     I (0));
+  T (1, "%#.0jX",    I (0));
 }
 
 /* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives
@@ -747,6 +821,24 @@ void test_sprintf_chk_l_const (void)
 
   T ( 8, "%8lu",     1L);         /* { dg-warning "nul past the end" } */
   T ( 9, "%8lu",     1L);
+
+  /*  As a special case, a precision of zero with an argument of zero
+      results in zero bytes (unless modified by width).  */
+  T (1, "%.0ld",     0L);
+  T (1, "%+.0ld",    0L);
+  T (1, "%-.0ld",    0L);
+  T (1, "% .0ld",    0L);
+  T (1, "%0.0ld",    0L);         /* { dg-warning ".0. flag ignored with precision" } */
+  T (1, "%00.0ld",   0L);         /* { dg-warning "repeated .0. flag in format" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%-0.0ld",   0L);         /* { dg-warning ".0. flag ignored with .-. flag" } */
+  /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */
+  T (1, "%.0li",     0L);
+  T (1, "%.0lo",     0L);
+  T (1, "%#.0lo",    0L);
+  T (1, "%.0lx",     0L);
+  T (1, "%.0lX",     0L);
+  T (1, "%#.0lX",    0L);
 }
 
 /* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives
@@ -858,37 +950,56 @@ void test_sprintf_chk_z_const (void)
 
 void test_sprintf_chk_a_const (void)
 {
-  T (-1, "%a",  0.0);
-  T (-1, "%la", 0.0);
+  T (-1, "%a",         0.0);
+  T (-1, "%la",        0.0);
+  T (-1, "%.a",        0.0);
+  T (-1, "%.la",       0.0);
+  T (-1, "%123.a",     0.0);
+  T (-1, "%234.la",    0.0);
+  T (-1, "%.345a",     0.0);
+  T (-1, "%456.567la", 0.0);
 
   /* The least number of bytes on output is 6 for "0x0p+0".  When precision
      is missing the number of digits after the decimal point isn't fully
      specified by C (it seems like a defect).  */
-  T (0, "%a",   0.0);          /* { dg-warning "into a region" } */
-  T (0, "%la",  0.0);          /* { dg-warning "into a region" } */
-  T (1, "%a",   0.0);          /* { dg-warning "into a region" } */
-  T (2, "%a",   0.0);          /* { dg-warning "into a region" } */
-  T (3, "%a",   0.0);          /* { dg-warning "into a region" } */
-  T (4, "%a",   0.0);          /* { dg-warning "into a region" } */
-  T (5, "%a",   0.0);          /* { dg-warning "into a region" } */
-  T (6, "%a",   0.0);          /* { dg-warning "writing a terminating nul" } */
+  T (0, "%a",   0.0);         /* { dg-warning "into a region" } */
+  T (0, "%la",  0.0);         /* { dg-warning "into a region" } */
+  T (1, "%a",   0.0);         /* { dg-warning "into a region" } */
+  T (2, "%a",   0.0);         /* { dg-warning "into a region" } */
+  T (3, "%a",   0.0);         /* { dg-warning "into a region" } */
+  T (4, "%a",   0.0);         /* { dg-warning "into a region" } */
+  T (5, "%a",   0.0);         /* { dg-warning "into a region" } */
+  T (6, "%a",   0.0);         /* { dg-warning "writing a terminating nul" } */
   T (7, "%a",   0.0);
 
-  T (0, "%.0a",   0.0);          /* { dg-warning "into a region" } */
-  T (0, "%.0la",  0.0);          /* { dg-warning "into a region" } */
-  T (1, "%.0a",   0.0);          /* { dg-warning "into a region" } */
-  T (2, "%.0a",   0.0);          /* { dg-warning "into a region" } */
-  T (3, "%.0a",   0.0);          /* { dg-warning "into a region" } */
-  T (4, "%.0a",   0.0);          /* { dg-warning "into a region" } */
-  T (5, "%.0a",   0.0);          /* { dg-warning "into a region" } */
-  T (6, "%.0a",   0.0);          /* { dg-warning "writing a terminating nul" } */
+  T (0, "%.a",    0.0);       /* { dg-warning "into a region" } */
+  T (0, "%.0a",   0.0);       /* { dg-warning "into a region" } */
+  T (0, "%.0la",  0.0);       /* { dg-warning "into a region" } */
+  T (1, "%.0a",   0.0);       /* { dg-warning "into a region" } */
+  T (2, "%.0a",   0.0);       /* { dg-warning "into a region" } */
+  T (3, "%.0a",   0.0);       /* { dg-warning "into a region" } */
+  T (4, "%.0a",   0.0);       /* { dg-warning "into a region" } */
+  T (5, "%.0a",   0.0);       /* { dg-warning "into a region" } */
+  T (6, "%.0a",   0.0);       /* { dg-warning "writing a terminating nul" } */
+
+  T (7, "%6.a",   0.0);
+  T (7, "%7.a",   0.0);       /* { dg-warning "writing a terminating nul" } */
+  T (7, "%7.1a",  0.0);       /* { dg-warning "writing 8 bytes into a region of size 7" } */
+
+  T (7, "%.a",    0.0);
   T (7, "%.0a",   0.0);
 }
 
 void test_sprintf_chk_e_const (void)
 {
-  T (-1, "%E",   0.0);
-  T (-1, "%lE",  0.0);
+  T (-1, "%E",      0.0);
+  T (-1, "%lE",     0.0);
+  T (-1, "%.E",     0.0);
+  T (-1, "%.lE",    0.0);
+  T (-1, "%123.E",  0.0);
+  T (-1, "%234.lE", 0.0);
+  T (-1, "%.345E",  0.0);
+  T (-1, "%.456lE", 0.0);
 
   T ( 0, "%E",   0.0);          /* { dg-warning "into a region" } */
   T ( 0, "%e",   0.0);          /* { dg-warning "into a region" } */
@@ -910,8 +1021,10 @@ void test_sprintf_chk_e_const (void)
   T (16, "%.8e", -1.9e+104);    /* { dg-warning "nul past the end" } */
   T (17, "%.8e", -2.0e+105);    /* -2.00000000e+105 */
 
+  T ( 5, "%.e",  0.0);          /* { dg-warning "nul past the end" } */
   T ( 5, "%.0e", 0.0);          /* { dg-warning "nul past the end" } */
   T ( 5, "%.0e", 1.0);          /* { dg-warning "nul past the end" } */
+  T ( 6, "%.e",  1.0);
   T ( 6, "%.0e", 1.0);
 
   /* The actual output of the following directives depends on the rounding
@@ -938,7 +1051,7 @@ void test_sprintf_chk_e_const (void)
    the value one, and unknown strings are assumed to have a zero
    length.  */
 
-void test_sprintf_chk_s_nonconst (int i, const char *s)
+void test_sprintf_chk_s_nonconst (int w, int p, const char *s)
 {
   T (-1, "%s",   s);
   T ( 0, "%s",   s);            /* { dg-warning "nul past the end" } */
@@ -946,6 +1059,19 @@ void test_sprintf_chk_s_nonconst (int i, const char *s)
   T ( 1, "%.0s", s);
   T ( 1, "%.1s", s);            /* { dg-warning "nul past the end" } */
 
+  /* The string argument is constant but the width and/or precision
+     is not.  */
+  T ( 1, "%*s",  w, "");
+  T ( 1, "%*s",  w, "1");       /* { dg-warning "nul past the end" } */
+  T ( 1, "%.*s", w, "");
+  T ( 1, "%.*s", w, "1");       /* { dg-warning "may write a terminating nul" } */
+  T ( 1, "%.*s", w, "123");     /* { dg-warning "writing between 0 and 3 bytes into a region of size 1" } */
+
+  T ( 1, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 1" } */
+  T ( 2, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 2" } */
+  T ( 3, "%*s", w, "123");      /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 4, "%*s", w, "123");
+
   /* The following will definitely write past the end of the buffer,
      but since at level 1 the length of an unknown string argument
      is assumed to be zero, it will write the terminating nul past
@@ -957,7 +1083,7 @@ void test_sprintf_chk_s_nonconst (int i, const char *s)
 /* Exercise the hh length modifier with all integer specifiers and
    a non-constant argument.  */
 
-void test_sprintf_chk_hh_nonconst (int a)
+void test_sprintf_chk_hh_nonconst (int w, int p, int a)
 {
   T (-1, "%hhd",        a);
 
@@ -999,11 +1125,48 @@ void test_sprintf_chk_hh_nonconst (int a)
   T (2, "%#hho",        a);     /* { dg-warning "nul past the end" } */
   T (2, "%#hhx",        a);     /* { dg-warning ".%#hhx. directive writing between 3 and . bytes into a region of size 2" } */
 
+  T (3, "%0hhd",        a);
+  T (3, "%1hhd",        a);
   T (3, "%2hhd",        a);
   T (3, "%2hhi",        a);
   T (3, "%2hho",        a);
   T (3, "%2hhu",        a);
   T (3, "%2hhx",        a);
+  T (3, "%2.hhx",       a);
+
+  T (3, "%3hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (3, "%3hhi",        a);     /* { dg-warning "nul past the end" } */
+  T (3, "%3hho",        a);     /* { dg-warning "nul past the end" } */
+  T (3, "%3hhu",        a);     /* { dg-warning "nul past the end" } */
+  T (3, "%3hhx",        a);     /* { dg-warning "nul past the end" } */
+  T (3, "%3.hhx",       a);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%5hhd",        a);     /* { dg-warning "into a region" } */
+  T (4, "%6hhi",        a);     /* { dg-warning "into a region" } */
+  T (4, "%7hho",        a);     /* { dg-warning "into a region" } */
+  T (4, "%8hhu",        a);     /* { dg-warning "into a region" } */
+  T (4, "%9hhx",        a);     /* { dg-warning "into a region" } */
+
+  T (3, "%.hhd",        a);
+  T (3, "%.0hhd",       a);
+  T (3, "%.1hhd",       a);
+  T (3, "%.2hhd",       a);
+  T (3, "%.2hhi",       a);
+  T (3, "%.2hho",       a);
+  T (3, "%.2hhu",       a);
+  T (3, "%.2hhx",       a);
+
+  T (3, "%.3hhd",       a);     /* { dg-warning "nul past the end" } */
+  T (3, "%.3hhi",       a);     /* { dg-warning "nul past the end" } */
+  T (3, "%.3hho",       a);     /* { dg-warning "nul past the end" } */
+  T (3, "%.3hhu",       a);     /* { dg-warning "nul past the end" } */
+  T (3, "%.3hhx",       a);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%.5hhd",       a);     /* { dg-warning "into a region" } */
+  T (4, "%.6hhi",       a);     /* { dg-warning "into a region" } */
+  T (4, "%.7hho",       a);     /* { dg-warning "into a region" } */
+  T (4, "%.8hhu",       a);     /* { dg-warning "into a region" } */
+  T (4, "%.9hhx",       a);     /* { dg-warning "into a region" } */
 
   /* Exercise cases where the type of the actual argument (whose value
      and range are unknown) constrain the size of the output and so
@@ -1012,6 +1175,55 @@ void test_sprintf_chk_hh_nonconst (int a)
   T (2, "%hhd", (UChar)a);
   T (2, "%hhi", (UChar)a);
   T (2, "%-hhi", (UChar)a);
+
+  /* Exercise cases where the argument is known but width isn't.  */
+  T (0, "%*hhi", w,   0);       /* { dg-warning "into a region" } */
+  T (1, "%*hhi", w,   0);       /* { dg-warning "nul past the end" } */
+  T (2, "%*hhi", w,   0);
+  T (2, "%*hhi", w,  12);       /* { dg-warning "nul past the end" } */
+  T (2, "%*hhi", w, 123);       /* { dg-warning "into a region" } */
+
+  /* The argument is known but precision isn't.  When the argument
+     is zero only the first call can be diagnosed since a zero
+     precision would result in no bytes on output.  */
+  T (0, "%.*hhi", p,   0);      /* { dg-warning "nul past the end" } */
+  T (1, "%.*hhi", p,   0);
+  T (2, "%.*hhi", p,   0);
+  T (2, "%.*hhi", p,  12);      /* { dg-warning "nul past the end" } */
+  T (2, "%.*hhi", p, 123);      /* { dg-warning "into a region" } */
+
+  /* The argument is known but neither width nor precision is.  */
+  T (0, "%*.*hhi", w, p,   0);  /* { dg-warning "nul past the end" } */
+  T (1, "%*.*hhi", w, p,   0);
+  T (2, "%*.*hhi", w, p,   0);
+  T (2, "%*.*hhi", w, p,  12);  /* { dg-warning "nul past the end" } */
+  T (2, "%*.*hhi", w, p, 123);  /* { dg-warning "into a region" } */
+
+  /* The argument and width are known but precision isn't.  */
+  T (0, "%1.*hhi",  p,   0);    /* { dg-warning "into a region" } */
+  T (0, "%-1.*hhi", p,   0);    /* { dg-warning "into a region" } */
+  T (1, "%1.*hhi",  p,   0);    /* { dg-warning "nul past the end" } */
+  T (2, "%1.*hhi",  p,   0);
+  T (2, "%2.*hhi",  p,   0);    /* { dg-warning "nul past the end" } */
+  T (2, "%1.*hhi",  p,  12);    /* { dg-warning "nul past the end" } */
+  T (2, "%2.*hhi",  p,  12);    /* { dg-warning "nul past the end" } */
+
+  T (2, "%1.*hhi",  p, 123);    /* { dg-warning "into a region" } */
+  T (2, "%2.*hhi",  p, 123);    /* { dg-warning "into a region" } */
+  T (2, "%3.*hhi",  p, 123);    /* { dg-warning "into a region" } */
+
+  /* The argument and precision are known but width isn't.  */
+  T (0, "%*.1hhi",  w,   0);    /* { dg-warning "into a region" } */
+  T (1, "%*.1hhi",  w,   0);    /* { dg-warning "nul past the end" } */
+  T (2, "%*.1hhi",  w,   0);
+  T (2, "%*.2hhi",  w,   0);    /* { dg-warning "nul past the end" } */
+  T (2, "%*.1hhi",  w,  12);    /* { dg-warning "nul past the end" } */
+  T (2, "%*.2hhi",  w,  12);    /* { dg-warning "nul past the end" } */
+  T (2, "%*.3hhi",  w,  12);    /* { dg-warning "into a region" } */
+
+  T (2, "%*.1hhi",  w, 123);    /* { dg-warning "into a region" } */
+  T (2, "%*.2hhi",  w, 123);    /* { dg-warning "into a region" } */
+  T (2, "%*.3hhi",  w, 123);    /* { dg-warning "into a region" } */
 }
 
 /* Exercise the h length modifier with all integer specifiers and
@@ -1063,7 +1275,7 @@ void test_sprintf_chk_h_nonconst (int a)
 /* Exercise all integer specifiers with no modifier and a non-constant
    argument.  */
 
-void test_sprintf_chk_int_nonconst (int a)
+void test_sprintf_chk_int_nonconst (int w, int p, int a)
 {
   T (-1, "%d",          a);
 
@@ -1104,12 +1316,22 @@ void test_sprintf_chk_int_nonconst (int a)
   T (3, "%2o",          a);
   T (3, "%2u",          a);
   T (3, "%2x",          a);
+
+  T (1, "%.*d",      p, a);
 }
 
-void test_sprintf_chk_e_nonconst (double d)
+void test_sprintf_chk_e_nonconst (int w, int p, double d)
 {
-  T (-1, "%E",          d);
-  T (-1, "%lE",         d);
+  T (-1, "%E",           d);
+  T (-1, "%lE",          d);
+  T (-1, "%.E",          d);
+  T (-1, "%.lE",         d);
+  T (-1, "%*E",    w,    d);
+  T (-1, "%*lE",   w,    d);
+  T (-1, "%.*E",      p, d);
+  T (-1, "%.*lE",     p, d);
+  T (-1, "%*.*E",  w, p, d);
+  T (-1, "%*.*lE", w, p, d);
 
   T ( 0, "%E",          d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
   T ( 0, "%e",          d);           /* { dg-warning "into a region" } */
@@ -1123,9 +1345,9 @@ void test_sprintf_chk_e_nonconst (double d)
   T (14, "%E",          d);
   T (14, "%e",          d);
 
-  T  (0, "%+E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
-  T  (0, "%-e",         d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
-  T  (0, "% E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
+  T 0, "%+E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
+  T 0, "%-e",         d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T 0, "% E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
 
   /* The range of output of "%.0e" is between 5 and 7 bytes (not counting
      the terminating NUL.  */
@@ -1136,6 +1358,9 @@ void test_sprintf_chk_e_nonconst (double d)
      the terminating NUL.  */
   T ( 7, "%.1e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
   T ( 8, "%.1e",        d);
+
+  T ( 0, "%*e",      0, d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T ( 0, "%*e",      w, d);           /* { dg-warning "writing 12 or more bytes into a region of size 0" } */
 }
 
 void test_sprintf_chk_f_nonconst (double d)
@@ -1204,7 +1429,6 @@ void test_vsprintf_chk_c (__builtin_va_list va)
   /* Here in the best case each argument will format as single character,
      causing the terminating NUL to be written past the end.  */
   T (3, "%lc%c%c");
-
 }
 
 void test_vsprintf_chk_int (__builtin_va_list va)
@@ -1254,9 +1478,11 @@ void test_vsprintf_chk_int (__builtin_va_list va)
 #define T(size, fmt, ...)                                              \
   __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__)
 
-void test_snprintf_c_const (void)
+void test_snprintf_c_const (char *d)
 {
-  T (-1, "%c",    0);            /* { dg-warning "specified destination size \[0-9\]+ too large" } */
+  T (-1, "%c",    0);            /* { dg-warning "specified destination size \[0-9\]+ is too large" } */
+
+  __builtin_snprintf (d, INT_MAX, "%c", 0);
 
   /* Verify the full text of the diagnostic for just the distinct messages
      and use abbreviations in subsequent test cases.  */
@@ -1306,7 +1532,7 @@ void test_snprintf_chk_c_const (void)
      the function by __builtin_object_size) is diagnosed.  */
   __builtin___snprintf_chk (buffer, 3, 0, 2, " ");   /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */
 
-  T (-1, "%c",    0);           /* { dg-warning "specified destination size \[^ \]* too large" } */
+  T (-1, "%c",    0);           /* { dg-warning "specified destination size \[^ \]* is too large" } */
 
   T (0, "%c",     0);
   T (0, "%c%c",   0, 0);
@@ -1417,7 +1643,7 @@ void test_vsprintf_int (__builtin_va_list va)
 
 void test_vsnprintf_s (__builtin_va_list va)
 {
-  T (-1, "%s");             /* { dg-warning "specified destination size \[^ \]* too large" } */
+  T (-1, "%s");             /* { dg-warning "specified destination size \[^ \]* is too large" } */
 
   T (0, "%s");
   T (1, "%s");
@@ -1442,7 +1668,7 @@ void test_vsnprintf_chk_s (__builtin_va_list va)
      the function by __builtin_object_size) is diagnosed.  */
   __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va);   /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */
 
-  __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va);   /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */
+  __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va);   /* { dg-warning "always overflow|destination size .\[0-9\]+. is too large" } */
 
   T (0, "%s");
   T (1, "%s");
index 8d97fa8..f4550ba 100644 (file)
@@ -1,6 +1,8 @@
 /* { dg-do compile } */
 /* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
 
+typedef __SIZE_TYPE__ size_t;
+
 #ifndef LINE
 #  define LINE 0
 #endif
@@ -232,3 +234,48 @@ void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
   T ( 4, "%i",  Ra (998,  999));
   T ( 4, "%i",  Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
 }
+
+/* Verify that destination size in excess of INT_MAX (and, separately,
+   in excess of the largest object) is diagnosed.  The former because
+   the functions are defined only for output of at most INT_MAX and
+   specifying a large upper bound defeats the bounds checking (and,
+   on some implementations such as Solaris, causes the function to
+   fail.  The latter because due to the limit of ptrdiff_t no object
+   can be larger than PTRDIFF_MAX bytes.  */
+
+void test_too_large (char *d, int x, __builtin_va_list va)
+{
+  const size_t imax = __INT_MAX__;
+  const size_t imax_p1 = imax + 1;
+
+  __builtin_snprintf (d, imax,    "%c", x);
+  __builtin_snprintf (d, imax_p1, "%c", x);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */
+  /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */
+
+  __builtin_vsnprintf (d, imax,    "%c", va);
+  __builtin_vsnprintf (d, imax_p1, "%c", va);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */
+  /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */
+
+  __builtin___snprintf_chk (d, imax,    0, imax,    "%c", x);
+  __builtin___snprintf_chk (d, imax_p1, 0, imax_p1, "%c", x);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */
+  /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */
+
+  __builtin___vsnprintf_chk (d, imax,    0, imax,    "%c", va);
+  __builtin___vsnprintf_chk (d, imax_p1, 0, imax_p1, "%c", va);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */
+  /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */
+
+  const size_t ptrmax = __PTRDIFF_MAX__;
+  const size_t ptrmax_m1 = ptrmax - 1;
+
+  __builtin_snprintf (d, ptrmax_m1, "%c", x);  /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */
+  __builtin_snprintf (d, ptrmax, "  %c", x);   /* { dg-warning "specified destination size \[0-9\]+ is too large" } */
+
+  __builtin_vsnprintf (d, ptrmax_m1, "%c", va);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */
+  __builtin_vsnprintf (d, ptrmax,    "%c", va);   /* { dg-warning "specified destination size \[0-9\]+ is too large" } */
+
+  __builtin___snprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", x);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */
+  __builtin___snprintf_chk (d, ptrmax,    0, ptrmax,    "%c", x);   /* { dg-warning "specified destination size \[0-9\]+ is too large" } */
+
+  __builtin___vsnprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", va);   /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */
+  __builtin___vsnprintf_chk (d, ptrmax,    0, ptrmax,    "%c", va);   /* { dg-warning "specified destination size \[0-9\]+ is too large" } */
+}