diff mbox

correct handling of non-constant width and precision (pr 78521)

Message ID 9b412cbb-1cb0-6341-5b85-78f235f7ae6f@gmail.com
State New
Headers show

Commit Message

Martin Sebor Nov. 28, 2016, 3:34 a.m. UTC
PR 78521 notes that the gimple-ssa-sprintf pass doesn't do the right
thing (i.e., the -Wformat-length and -fprintf-return-value options
behave incorrectly) when a conversion specification includes a width
or precision with a non-constant value.  The code treats such cases
as if they were not provided which is incorrect and results in
the wrong bytes counts in warning messages and in the wrong ranges
being generated for such calls (or in the case sprintf(0, 0, ...)
for some such calls being eliminated).

The attached patch corrects the handling of these cases, plus a couple
of other edge cases in the same area: it adjusts the parser to accept
precision in the form of just a period with no asterisk or decimal
digits after it (this sets the precision to zero), and corrects the
handling of zero precision and zero argument in integer directives
to produce no bytes on output.

Finally, the patch also tightens up the constraint on the upper bound
of bounded functions like snprintf to be INT_MAX.  The functions cannot
produce output in excess of INT_MAX + 1 bytes and some implementations
(e.g., Solaris) fail with EINVAL when the bound is INT_MAX or more.
This is the subject of PR 78520.

Thanks
Martin

Comments

Jeff Law Nov. 28, 2016, 8:26 p.m. UTC | #1
On 11/27/2016 08:34 PM, Martin Sebor wrote:
> PR 78521 notes that the gimple-ssa-sprintf pass doesn't do the right

> thing (i.e., the -Wformat-length and -fprintf-return-value options

> behave incorrectly) when a conversion specification includes a width

> or precision with a non-constant value.  The code treats such cases

> as if they were not provided which is incorrect and results in

> the wrong bytes counts in warning messages and in the wrong ranges

> being generated for such calls (or in the case sprintf(0, 0, ...)

> for some such calls being eliminated).

>

> The attached patch corrects the handling of these cases, plus a couple

> of other edge cases in the same area: it adjusts the parser to accept

> precision in the form of just a period with no asterisk or decimal

> digits after it (this sets the precision to zero), and corrects the

> handling of zero precision and zero argument in integer directives

> to produce no bytes on output.

>

> Finally, the patch also tightens up the constraint on the upper bound

> of bounded functions like snprintf to be INT_MAX.  The functions cannot

> produce output in excess of INT_MAX + 1 bytes and some implementations

> (e.g., Solaris) fail with EINVAL when the bound is INT_MAX or more.

> This is the subject of PR 78520.

>

> Thanks

> Martin

>

> gcc-78521.diff

>

>

> 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/78521

> 	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/78521

> 	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.

Fairly dense reading.  Probably inherent due to the problem space.

OK for the trunk.

Thanks,
jeff
Joseph Myers Nov. 29, 2016, 12:42 a.m. UTC | #2
On Sun, 27 Nov 2016, Martin Sebor wrote:

> Finally, the patch also tightens up the constraint on the upper bound

> of bounded functions like snprintf to be INT_MAX.  The functions cannot

> produce output in excess of INT_MAX + 1 bytes and some implementations

> (e.g., Solaris) fail with EINVAL when the bound is INT_MAX or more.

> This is the subject of PR 78520.


Note that failing with large bounds is questionable (there is an apparent 
conflict between ISO C, where passing a large bound seems valid, and 
POSIX, where large bounds require errors; see 
<http://austingroupbugs.net/view.php?id=761>; I'm not sure if any liaison 
issue for this ever got passed to WG14).

-- 
Joseph S. Myers
joseph@codesourcery.com
Martin Sebor Nov. 29, 2016, 4:56 p.m. UTC | #3
On 11/28/2016 05:42 PM, Joseph Myers wrote:
> On Sun, 27 Nov 2016, Martin Sebor wrote:

>

>> Finally, the patch also tightens up the constraint on the upper bound

>> of bounded functions like snprintf to be INT_MAX.  The functions cannot

>> produce output in excess of INT_MAX + 1 bytes and some implementations

>> (e.g., Solaris) fail with EINVAL when the bound is INT_MAX or more.

>> This is the subject of PR 78520.

>

> Note that failing with large bounds is questionable (there is an apparent

> conflict between ISO C, where passing a large bound seems valid, and

> POSIX, where large bounds require errors; see

> <http://austingroupbugs.net/view.php?id=761>; I'm not sure if any liaison

> issue for this ever got passed to WG14).


Thanks!  That's useful background.  Let me check with Nick to see
is he (as the POSIX/WG14 liaison) plans to submit it.  I can also
write it up for the next WG14 meeting if we or the Austin Group
feel like WG14 should clarify or change things.

Martin
Martin Sebor Nov. 29, 2016, 8:14 p.m. UTC | #4
On 11/29/2016 09:56 AM, Martin Sebor wrote:
> On 11/28/2016 05:42 PM, Joseph Myers wrote:

>> On Sun, 27 Nov 2016, Martin Sebor wrote:

>>

>>> Finally, the patch also tightens up the constraint on the upper bound

>>> of bounded functions like snprintf to be INT_MAX.  The functions cannot

>>> produce output in excess of INT_MAX + 1 bytes and some implementations

>>> (e.g., Solaris) fail with EINVAL when the bound is INT_MAX or more.

>>> This is the subject of PR 78520.

>>

>> Note that failing with large bounds is questionable (there is an apparent

>> conflict between ISO C, where passing a large bound seems valid, and

>> POSIX, where large bounds require errors; see

>> <http://austingroupbugs.net/view.php?id=761>; I'm not sure if any liaison

>> issue for this ever got passed to WG14).

>

> Thanks!  That's useful background.  Let me check with Nick to see

> is he (as the POSIX/WG14 liaison) plans to submit it.  I can also

> write it up for the next WG14 meeting if we or the Austin Group

> feel like WG14 should clarify or change things.


I've been looking at the original BSD sources where snprintf came
from (AFAICT).  The first implementation I could find is in Net/2
from 1988.  It returns EOF when the size after conversion to int
is less than 1.  The same code is still in 4.4BSD.

Early UNIX implementations also have the limitation that the buffer
size maintained by struct FILE is an int.  Since snprintf on these
early implementations usually uses vfprintf to do the work (with
the count being set to the snprinf bound), it can't store more than
INT_MAX bytes without overflowing the counter.

http://minnie.tuhs.org/cgi-bin/utree.pl?file=Net2/usr/src/lib/libc/stdio/snprintf.c

It looks to me like the POSIX spec is faithful to the historical
implementations and C should consider either tightening up its
constraints or make the behavior implementation-defined to allow
for more modern implementations that don't have this restriction.

Martin
diff mbox

Patch

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/78521
	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/78521
	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.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index dc2b66d..9f07503 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -235,23 +235,12 @@  target_int_min ()
   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 the value of INT_MAX for the target.  */
 
 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 +248,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
@@ -843,6 +832,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, 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
@@ -860,18 +886,10 @@  format_integer (const conversion_spec &spec, tree arg)
   if (!intmax_type_node)
     build_intmax_type_nodes (&intmax_type_node, &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';
 
@@ -940,15 +958,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;
@@ -981,25 +992,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;
     }
@@ -1110,8 +1152,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);
@@ -1261,11 +1305,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;
       }
 
@@ -1278,13 +1324,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;
       }
 
@@ -1300,10 +1349,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':
@@ -1317,10 +1367,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;
       }
 
@@ -1346,6 +1397,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;
 
@@ -1358,12 +1412,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)
@@ -1374,6 +1429,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;
 	}
@@ -1413,9 +1469,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
@@ -1442,16 +1498,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;
     }
 
@@ -1521,20 +1585,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;
 
@@ -1594,11 +1648,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)
 	    {
@@ -1626,19 +1681,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
 	{
@@ -1652,10 +1720,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;
 	    }
@@ -1678,20 +1746,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;
@@ -1807,7 +1878,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
@@ -1821,6 +1892,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'
@@ -2277,13 +2361,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
@@ -2328,6 +2421,7 @@  pass_sprintf_length::compute_format_length (const call_info &info,
 	    }
 	}
 
+    start_precision:
       if ('.' == *pf)
 	{
 	  ++pf;
@@ -2345,7 +2439,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)
@@ -2705,9 +2804,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))
@@ -2719,10 +2818,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)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
index d568f9c..cdaeeac 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
@@ -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
index 0000000..375fc09
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c
@@ -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"} } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
index a24889b..4aafc9f 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -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");
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
index 8d97fa8..f4550ba 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
@@ -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" } */
+}