diff mbox

[5/5] add support for width and precision ranges (PR 78703)

Message ID fcf43443-f0b0-7da3-44ba-3fe3cc0dd96e@gmail.com
State New
Headers show

Commit Message

Martin Sebor Jan. 22, 2017, 11:53 p.m. UTC
This is the last patch in the series.  It adds logic to handle
non-constant width and precision with range information to help
reduce both false positives false negatives.  The patch replaces
the scalar width and precision with two element arrays throughout
the pass and makes adjustments to reflect their bounds in the byte
counters.  Since the basic infrastructure for this is present in
the code the changes are fairly mechanical.

Comments

Jeff Law Jan. 23, 2017, 8:53 p.m. UTC | #1
On 01/22/2017 04:53 PM, Martin Sebor wrote:
> This is the last patch in the series.  It adds logic to handle
> non-constant width and precision with range information to help
> reduce both false positives false negatives.  The patch replaces
> the scalar width and precision with two element arrays throughout
> the pass and makes adjustments to reflect their bounds in the byte
> counters.  Since the basic infrastructure for this is present in
> the code the changes are fairly mechanical.
>
> gcc-78703-5.diff
>
>
> commit c0a1f67fedb531abaf4760e8cd5b9b037ef5d4c4
> Author: Martin Sebor <msebor@redhat.com>
> Date:   Sun Jan 22 12:37:33 2017 -0700
>
>     2017-01-22  Martin Sebor  <msebor@redhat.com>
>
>     	* gimple-ssa-sprintf.c (adjust_for_width_or_precision): Change
>     	to accept adjustment as an array.
>     	(get_int_range): New function.
>     	(struct directive): Make width and prec arrays.
>     	(directive::set_width, directive::set_precision): Call get_int_range.
>     	(format_integer, format_floating): Handle width and precision ranges.
>     	(format_string, parse_directive): Same.
>
>     	gcc/testsuite:
>     	* gcc.dg/tree-ssa/builtin-snprintf-warn-1.c: Update
>     	* gcc.dg/tree-ssa/builtin-sprintf-warn-9.c: Rename...
>     	* gcc.dg/tree-ssa/builtin-sprintf-warn-10.c: ...to this.
>     	* gcc.dg/tree-ssa/builtin-sprintf-warn-9.c: New test.
I'm still working on #4, which is still fairly large.  I needed to step 
away from it for a bit.

Regardless #5 is OK.

Jeff
Markus Trippelsdorf Jan. 27, 2017, 7:44 a.m. UTC | #2
On 2017.01.22 at 16:53 -0700, Martin Sebor wrote:
> This is the last patch in the series.  It adds logic to handle
> non-constant width and precision with range information to help
> reduce both false positives false negatives.  The patch replaces
> the scalar width and precision with two element arrays throughout
> the pass and makes adjustments to reflect their bounds in the byte
> counters.  Since the basic infrastructure for this is present in
> the code the changes are fairly mechanical.

> commit c0a1f67fedb531abaf4760e8cd5b9b037ef5d4c4
> Author: Martin Sebor <msebor@redhat.com>
> Date:   Sun Jan 22 12:37:33 2017 -0700
> 
>     2017-01-22  Martin Sebor  <msebor@redhat.com>
>     
>     	* gimple-ssa-sprintf.c (adjust_for_width_or_precision): Change
>     	to accept adjustment as an array.
>     	(get_int_range): New function.
>     	(struct directive): Make width and prec arrays.
>     	(directive::set_width, directive::set_precision): Call get_int_range.
>     	(format_integer, format_floating): Handle width and precision ranges.
>     	(format_string, parse_directive): Same.

This is the third time that you broke bootstrap with MPFR 2.x.x:

 gcc/gimple-ssa-sprintf.c:1501: error: 'MPFR_RNDN' was not declared in this scope 

Please be more careful in the future.
Martin Sebor Jan. 27, 2017, 3:45 p.m. UTC | #3
On 01/27/2017 12:44 AM, Markus Trippelsdorf wrote:
> On 2017.01.22 at 16:53 -0700, Martin Sebor wrote:
>> This is the last patch in the series.  It adds logic to handle
>> non-constant width and precision with range information to help
>> reduce both false positives false negatives.  The patch replaces
>> the scalar width and precision with two element arrays throughout
>> the pass and makes adjustments to reflect their bounds in the byte
>> counters.  Since the basic infrastructure for this is present in
>> the code the changes are fairly mechanical.
>
>> commit c0a1f67fedb531abaf4760e8cd5b9b037ef5d4c4
>> Author: Martin Sebor <msebor@redhat.com>
>> Date:   Sun Jan 22 12:37:33 2017 -0700
>>
>>     2017-01-22  Martin Sebor  <msebor@redhat.com>
>>
>>     	* gimple-ssa-sprintf.c (adjust_for_width_or_precision): Change
>>     	to accept adjustment as an array.
>>     	(get_int_range): New function.
>>     	(struct directive): Make width and prec arrays.
>>     	(directive::set_width, directive::set_precision): Call get_int_range.
>>     	(format_integer, format_floating): Handle width and precision ranges.
>>     	(format_string, parse_directive): Same.
>
> This is the third time that you broke bootstrap with MPFR 2.x.x:



>
>  gcc/gimple-ssa-sprintf.c:1501: error: 'MPFR_RNDN' was not declared in this scope
>
> Please be more careful in the future.

I'm sorry, but I'm not sure how to be more careful here.  I can
realistically only build and test with one version of MPFR.  I
proposed a solution to this problem in the past: to download
the oldest supported version of it and the other dependencies:

   https://gcc.gnu.org/ml/gcc-patches/2016-09/msg01596.html

That was rejected by those who think GCC should target the latest,
which is what I have been doing.  So I suggested an alternative
solution in response to your last message about this problem:

   https://gcc.gnu.org/ml/gcc-patches/2017-01/msg01886.html

I haven't had a chance to implement it yet.  You are welcome to
contribute a patch in the meantime to help forestall any further
accidental breakage.

Martin
Markus Trippelsdorf Jan. 27, 2017, 3:53 p.m. UTC | #4
On 2017.01.27 at 08:45 -0700, Martin Sebor wrote:
> On 01/27/2017 12:44 AM, Markus Trippelsdorf wrote:
> > On 2017.01.22 at 16:53 -0700, Martin Sebor wrote:
> > > This is the last patch in the series.  It adds logic to handle
> > > non-constant width and precision with range information to help
> > > reduce both false positives false negatives.  The patch replaces
> > > the scalar width and precision with two element arrays throughout
> > > the pass and makes adjustments to reflect their bounds in the byte
> > > counters.  Since the basic infrastructure for this is present in
> > > the code the changes are fairly mechanical.
> > 
> > > commit c0a1f67fedb531abaf4760e8cd5b9b037ef5d4c4
> > > Author: Martin Sebor <msebor@redhat.com>
> > > Date:   Sun Jan 22 12:37:33 2017 -0700
> > > 
> > >     2017-01-22  Martin Sebor  <msebor@redhat.com>
> > > 
> > >     	* gimple-ssa-sprintf.c (adjust_for_width_or_precision): Change
> > >     	to accept adjustment as an array.
> > >     	(get_int_range): New function.
> > >     	(struct directive): Make width and prec arrays.
> > >     	(directive::set_width, directive::set_precision): Call get_int_range.
> > >     	(format_integer, format_floating): Handle width and precision ranges.
> > >     	(format_string, parse_directive): Same.
> > 
> > This is the third time that you broke bootstrap with MPFR 2.x.x:
> 
> 
> 
> > 
> >  gcc/gimple-ssa-sprintf.c:1501: error: 'MPFR_RNDN' was not declared in this scope
> > 
> > Please be more careful in the future.
> 
> I'm sorry, but I'm not sure how to be more careful here.  I can
> realistically only build and test with one version of MPFR.  I
> proposed a solution to this problem in the past: to download
> the oldest supported version of it and the other dependencies:
> 
>   https://gcc.gnu.org/ml/gcc-patches/2016-09/msg01596.html
> 
> That was rejected by those who think GCC should target the latest,
> which is what I have been doing.  So I suggested an alternative
> solution in response to your last message about this problem:
> 
>   https://gcc.gnu.org/ml/gcc-patches/2017-01/msg01886.html
> 
> I haven't had a chance to implement it yet.  You are welcome to
> contribute a patch in the meantime to help forestall any further
> accidental breakage.

Fortunately this was already done by Jakub's r244966.
"MPFR_RNDN MPFR_RNDZ MPFR_RNDU MPFR_RNDD" are now "poisoned", so the issue
cannot happen again.
diff mbox

Patch

commit c0a1f67fedb531abaf4760e8cd5b9b037ef5d4c4
Author: Martin Sebor <msebor@redhat.com>
Date:   Sun Jan 22 12:37:33 2017 -0700

    2017-01-22  Martin Sebor  <msebor@redhat.com>
    
    	* gimple-ssa-sprintf.c (adjust_for_width_or_precision): Change
    	to accept adjustment as an array.
    	(get_int_range): New function.
    	(struct directive): Make width and prec arrays.
    	(directive::set_width, directive::set_precision): Call get_int_range.
    	(format_integer, format_floating): Handle width and precision ranges.
    	(format_string, parse_directive): Same.
    
    	gcc/testsuite:
    	* gcc.dg/tree-ssa/builtin-snprintf-warn-1.c: Update
    	* gcc.dg/tree-ssa/builtin-sprintf-warn-9.c: Rename...
    	* gcc.dg/tree-ssa/builtin-sprintf-warn-10.c: ...to this.
    	* gcc.dg/tree-ssa/builtin-sprintf-warn-9.c: New test.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index eec133a..111158d 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -478,15 +478,15 @@  struct fmtresult
     range.unlikely = max;
   }
 
-  /* Adjust result upward to reflect the value the specified width
-     or precision is known to have.  */
-  fmtresult& adjust_for_width_or_precision (HOST_WIDE_INT,
+  /* Adjust result upward to reflect the RANGE of values the specified
+     width or precision is known to be in.  */
+  fmtresult& adjust_for_width_or_precision (const HOST_WIDE_INT[2],
 					    tree = NULL_TREE,
 					    unsigned = 0, unsigned = 0);
 
   /* Return the maximum number of decimal digits a value of TYPE
      formats as on output.  */
-  static unsigned type_max_digits (tree type, int base);
+  static unsigned type_max_digits (tree, int);
 
   /* The range a directive's argument is in.  */
   tree argmin, argmax;
@@ -504,32 +504,21 @@  struct fmtresult
   bool nullp;
 };
 
-/* Adjust result upward to reflect the value SCALAR_ADJUST the specified
-   width or precision is known to have.  When non-null, TYPE denotes the
-   type of the directive whose result is being adjusted, BASE gives the
-   base of the directive (octal, decimal, or hex), and ADJ denotes
-   the additional adjustment to the LIKELY counter that may need to be
-   added when SCALAR_ADJUST represents a range.  */
+/* Adjust result upward to reflect the range ADJUST of values the
+   specified width or precision is known to be in.  When non-null,
+   TYPE denotes the type of the directive whose result is being
+   adjusted, BASE gives the base of the directive (octal, decimal,
+   or hex), and ADJ denotes the additional adjustment to the LIKELY
+   counter that may need to be added when ADJUST is a range.  */
 
 fmtresult&
-fmtresult::adjust_for_width_or_precision (HOST_WIDE_INT scalar_adjust,
+fmtresult::adjust_for_width_or_precision (const HOST_WIDE_INT adjust[2],
 					  tree type /* = NULL_TREE */,
 					  unsigned base /* = 0 */,
 					  unsigned adj /* = 0 */)
 {
   bool minadjusted = false;
 
-  /* Translate SCALAR_ADJUST to a "fake" range until width and precision
-     ranges are handled.  */
-  HOST_WIDE_INT adjust[2];
-  if (scalar_adjust == HOST_WIDE_INT_MIN)
-    {
-      adjust[0] = -1;
-      adjust[1] = target_int_max () + 1;
-    }
-  else
-    adjust[0] = adjust[1] = scalar_adjust;
-
   /* Adjust the minimum and likely counters.  */
   if (0 <= adjust[0])
     {
@@ -609,7 +598,12 @@  fmtresult::type_max_digits (tree type, int base)
   return prec * 301 / 1000 + 1;
 }
 
-/* Description of a format directive.  */
+static bool
+get_int_range (tree, tree, HOST_WIDE_INT *, HOST_WIDE_INT *,
+	       bool, HOST_WIDE_INT);
+
+/* Description of a format directive.  A directive is either a plain
+   string or a conversion specification that starts with '%'.  */
 
 struct directive
 {
@@ -623,10 +617,11 @@  struct directive
   /* A bitmap of flags, one for each character.  */
   unsigned flags[256 / sizeof (int)];
 
-  /* The specified width, or -1 if not specified.  */
-  HOST_WIDE_INT width;
-  /* The specified precision, or -1 if not specified.  */
-  HOST_WIDE_INT prec;
+  /* The range of values of the specified width, or -1 if not specified.  */
+  HOST_WIDE_INT width[2];
+  /* The range of values of the specified precision, or -1 if not
+     specified.  */
+  HOST_WIDE_INT prec[2];
 
   /* Length modifier.  */
   format_lengths modifier;
@@ -666,54 +661,36 @@  struct directive
       &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
   }
 
-  /* Set the width to VAL.  */
+  /* Set both bounds of the width range to VAL.  */
   void set_width (HOST_WIDE_INT val)
   {
-    width = val;
+    width[0] = width[1] = val;
   }
 
-  /* Set the width to ARG.  */
+  /* Set the width range according to ARG, with both bounds being
+     no less than 0.  For a constant ARG set both bounds to its value
+     or 0, whichever is greater.  For a non-constant ARG in some range
+     set width to its range adjusting each bound to -1 if it's less.
+     For an indeterminate ARG set width to [0, INT_MAX].  */
   void set_width (tree arg)
   {
-    if (tree_fits_shwi_p (arg))
-      {
-	width = tree_to_shwi (arg);
-	if (width < 0)
-	  {
-	    if (width == HOST_WIDE_INT_MIN)
-	      {
-		/* Avoid undefined behavior due to negating a minimum.
-		   This case will be diagnosed since it will result in
-		   more than INT_MAX bytes on output, either by the
-		   directive itself (when INT_MAX < HOST_WIDE_INT_MAX)
-		   or by the format function itself.  */
-		width = HOST_WIDE_INT_MAX;
-	      }
-	    else
-	      width = -width;
-	  }
-      }
-    else
-      width = HOST_WIDE_INT_MIN;
+    get_int_range (arg, integer_type_node, width, width + 1, true, 0);
   }
 
-  /* Set the precision to val.  */
+  /* Set both bounds of the precision range to VAL.  */
   void set_precision (HOST_WIDE_INT val)
   {
-    prec = val;
+    prec[0] = prec[1] = val;
   }
 
-  /* Set the precision to ARG.  */
+  /* Set the precision range according to ARG, with both bounds being
+     no less than -1.  For a constant ARG set both bounds to its value
+     or -1 whichever is greater.  For a non-constant ARG in some range
+     set precision to its range adjusting each bound to -1 if it's less.
+     For an indeterminate ARG set precision to [-1, INT_MAX].  */
   void set_precision (tree arg)
   {
-    if (tree_fits_shwi_p (arg))
-      {
-	prec = tree_to_shwi (arg);
-	if (prec < 0)
-	  prec = -1;
-      }
-    else
-      prec = HOST_WIDE_INT_MIN;
+    get_int_range (arg, integer_type_node, prec, prec + 1, false, -1);
   }
 };
 
@@ -797,7 +774,7 @@  tree_digits (tree x, int base, HOST_WIDE_INT prec, bool plus, bool prefix)
 }
 
 /* Given the formatting result described by RES and NAVAIL, the number
-   of available in the destination, return the number of bytes remaining
+   of available in the destination, return the range of bytes remaining
    in the destination.  */
 
 static inline result_range
@@ -816,9 +793,6 @@  bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
      minus the minimum.  */
   range.max = res.range.min < navail ? navail - res.range.min : 0;
 
-  /* Given the formatting result described by RES and NAVAIL, the number
-     of available in the destination, return the minimum number of bytes
-     remaining in the destination.  */
   range.likely = res.range.likely < navail ? navail - res.range.likely : 0;
 
   if (res.range.max < HOST_WIDE_INT_MAX)
@@ -932,6 +906,96 @@  build_intmax_type_nodes (tree *pintmax, tree *puintmax)
     }
 }
 
+/* Determine the range [*PMIN, *PMAX] that the expression ARG of TYPE
+   is in.  Return true when the range is a subrange of that of TYPE.
+   Whn ARG is null it is as if it had the full range of TYPE.
+   When ABSOLUTE is true the range reflects the absolute value of
+   the argument.  When ABSOLUTE is false, negative bounds of
+   the determined range are replaced with NEGBOUND.  */
+
+static bool
+get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
+	       bool absolute, HOST_WIDE_INT negbound)
+{
+  bool knownrange = false;
+
+  if (!arg)
+    {
+      *pmin = (TYPE_UNSIGNED (type)
+	       ? tree_to_uhwi (TYPE_MIN_VALUE (type))
+	       : tree_to_shwi (TYPE_MIN_VALUE (type)));
+      *pmax = tree_to_uhwi (TYPE_MAX_VALUE (type));
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
+    {
+      /* For a constant argument return its value adjusted as specified
+	 by NEGATIVE and NEGBOUND and return true to indicate that the
+	 result is known.  */
+      *pmin = tree_fits_shwi_p (arg) ? tree_to_shwi (arg) : tree_to_uhwi (arg);
+      *pmax = *pmin;
+      knownrange = true;
+    }
+  else
+    {
+      /* True if the argument's range cannot be determined.  */
+      bool unknown = true;
+
+      type = TREE_TYPE (arg);
+
+      if (TREE_CODE (arg) == SSA_NAME
+	  && TREE_CODE (type) == INTEGER_TYPE)
+	{
+	  /* Try to determine the range of values of the integer argument.  */
+	  wide_int min, max;
+	  enum value_range_type range_type = get_range_info (arg, &min, &max);
+	  if (range_type == VR_RANGE)
+	    {
+	      HOST_WIDE_INT type_min
+		= (TYPE_UNSIGNED (type)
+		   ? tree_to_uhwi (TYPE_MIN_VALUE (type))
+		   : tree_to_shwi (TYPE_MIN_VALUE (type)));
+
+	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (type));
+
+	      *pmin = min.to_shwi ();
+	      *pmax = max.to_shwi ();
+
+	      /* Return true if the adjusted range is a subrange of
+		 the full range of the argument's type.  */
+	      knownrange = type_min < *pmin || *pmax < type_max;
+
+	      unknown = false;
+	    }
+	}
+
+      /* Handle an argument with an unknown range as if none had been
+	 provided.  */
+      if (unknown)
+	return get_int_range (NULL_TREE, type, pmin, pmax, absolute, negbound);
+    }
+
+  /* Adjust each bound as specified by ABSOLUTE and NEGBOUND.  */
+  if (absolute)
+    {
+      if (*pmin < 0)
+	{
+	  if (*pmin == *pmax)
+	    *pmin = *pmax = -*pmin;
+	  else
+	    {
+	      HOST_WIDE_INT tmp = -*pmin;
+	      *pmin = 0;
+	      if (*pmax < tmp)
+		*pmax = tmp;
+	    }
+	}
+    }
+  else if (*pmin < negbound)
+    *pmin = negbound;
+
+  return knownrange;
+}
+
 /* With the range [*ARGMIN, *ARGMAX] of an integer directive's actual
    argument, due to the conversion from either *ARGMIN or *ARGMAX to
    the type of the directive's formal argument it's possible for both
@@ -996,8 +1060,9 @@  adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax)
 }
 
 /* Return a range representing the minimum and maximum number of bytes
-   that the directive DIR will write on output for the integer argument
-   ARG when non-null.  ARG may be null (for vararg functions).  */
+   that the format directive DIR will output for any argument given
+   the WIDTH and PRECISION (extracted from DIR).  This function is
+   used when the directive argument or its value isn't known.  */
 
 static fmtresult
 format_integer (const directive &dir, tree arg)
@@ -1109,11 +1174,9 @@  format_integer (const directive &dir, tree arg)
     {
       /* When a constant argument has been provided use its value
 	 rather than type to determine the length of the output.  */
-
       fmtresult res;
 
-      if ((dir.prec == HOST_WIDE_INT_MIN || dir.prec == 0)
-	  && integer_zerop (arg))
+      if ((dir.prec[0] <= 0 && dir.prec[1] >= 0) && integer_zerop (arg))
 	{
 	  /* As a special case, a precision of zero with a zero argument
 	     results in zero bytes except in base 8 when the '#' flag is
@@ -1123,7 +1186,7 @@  format_integer (const directive &dir, tree arg)
 	     effect).  This must extend to the case of a specified precision
 	     with an unknown value because it can be zero.  */
 	  res.range.min = ((base == 8 && dir.get_flag ('#')) || maybesign);
-	  if (res.range.min == 0 && dir.prec == HOST_WIDE_INT_MIN)
+	  if (res.range.min == 0 && dir.prec[0] != dir.prec[1])
 	    {
 	      res.range.max = 1;
 	      res.range.likely = 1;
@@ -1139,17 +1202,13 @@  format_integer (const directive &dir, tree arg)
 	  /* Convert the argument to the type of the directive.  */
 	  arg = fold_convert (dirtype, arg);
 
-	  res.range.min = tree_digits (arg, base, dir.prec,
+	  res.range.min = tree_digits (arg, base, dir.prec[0],
 				       maybesign, maybebase);
-
-	  /* Set the maximum to INT_MAX when precision is specified
-	     but unknown (because it can be as large as that) otherwise
-	     to the minimum and have it adjusted below.  */
-	  if (dir.prec == HOST_WIDE_INT_MIN)
-	    res.range.max = target_int_max ();
-	  else
+	  if (dir.prec[0] == dir.prec[1])
 	    res.range.max = res.range.min;
-
+	  else
+	    res.range.max = tree_digits (arg, base, dir.prec[1],
+					 maybesign, maybebase);
 	  res.range.likely = res.range.min;
 	}
 
@@ -1161,6 +1220,7 @@  format_integer (const directive &dir, tree arg)
       /* Bump up the counters again if PRECision is greater still.  */
       res.adjust_for_width_or_precision (dir.prec, dirtype, base,
 					 (sign | maybebase) + (base == 16));
+
       return res;
     }
   else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
@@ -1245,7 +1305,7 @@  format_integer (const directive &dir, tree arg)
 	 can output.  When precision may be zero, use zero as the minimum
 	 since it results in no bytes on output (unless width is specified
 	 to be greater than 0).  */
-      bool zero = dir.prec == 0 || dir.prec == HOST_WIDE_INT_MIN;
+      bool zero = dir.prec[0] <= 0 && dir.prec[1] >= 0;
       argmin = build_int_cst (argtype, !zero);
 
       int typeprec = TYPE_PRECISION (dirtype);
@@ -1429,16 +1489,15 @@  format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
      round-to-nearest mode.  */
   mpfr_t x;
   mpfr_init2 (x, rfmt->p);
-  mpfr_from_real (x, &rv, GMP_RNDN);
+  mpfr_from_real (x, &rv, MPFR_RNDN);
 
   /* Return a value one greater to account for the leading minus sign.  */
   return 1 + get_mpfr_format_length (x, "", prec, spec, 'D');
 }
 
 /* Return a range representing the minimum and maximum number of bytes
-   that the format directive DIR will output for any argument given
-   the WIDTH and PRECISION (extracted from DIR).  This function is
-   used when the directive argument or its value isn't known.  */
+   that the directive DIR will output for any argument.  This function
+   is used when the directive argument or its value isn't known.  */
 
 static fmtresult
 format_floating (const directive &dir)
@@ -1484,10 +1543,10 @@  format_floating (const directive &dir)
     case 'a':
       {
 	HOST_WIDE_INT minprec = 6 + !radix /* decimal point */;
-	if (dir.prec <= 0)
+	if (dir.prec[0] <= 0)
 	  minprec = 0;
-	else if (dir.prec > 0)
-	  minprec = dir.prec + !radix /* decimal point */;
+	else if (dir.prec[0] > 0)
+	  minprec = dir.prec[0] + !radix /* decimal point */;
 
 	res.range.min = (2 /* 0x */
 			 + flagmin
@@ -1495,17 +1554,15 @@  format_floating (const directive &dir)
 			 + minprec
 			 + 3 /* p+0 */);
 
-	HOST_WIDE_INT maxprec
-	  = dir.prec == HOST_WIDE_INT_MIN ? target_int_max () : dir.prec;
-	res.range.max = format_floating_max (type, 'a', maxprec);
+	res.range.max = format_floating_max (type, 'a', dir.prec[1]);
 	res.range.likely = res.range.min;
 
 	/* The unlikely maximum accounts for the longest multibyte
 	   decimal point character.  */
-	if (dir.prec != 0)
-	  res.range.unlikely = res.range.max + target_mb_len_max () - 1;
-	else
-	  res.range.unlikely = res.range.max;
+	res.range.unlikely = res.range.max;
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely += target_mb_len_max () - 1;
 
 	break;
       }
@@ -1516,28 +1573,26 @@  format_floating (const directive &dir)
 	/* The minimum output is "[-+]1.234567e+00" regardless
 	   of the value of the actual argument.  */
 	HOST_WIDE_INT minprec = 6 + !radix /* decimal point */;
-	if (dir.prec == HOST_WIDE_INT_MIN || dir.prec == 0)
+	if ((dir.prec[0] < 0 && dir.prec[1] > -1) || dir.prec[0] == 0)
 	  minprec = 0;
-	else if (dir.prec > 0)
-	  minprec = dir.prec + !radix /* decimal point */;
+	else if (dir.prec[0] > 0)
+	  minprec = dir.prec[0] + !radix /* decimal point */;
 
 	res.range.min = (flagmin
 			 + radix
 			 + minprec
 			 + 2 /* e+ */ + 2);
-
 	/* MPFR uses a precision of 16 by default for some reason.
 	   Set it to the C default of 6.  */
-	HOST_WIDE_INT maxprec
-	  = (dir.prec == HOST_WIDE_INT_MIN ? target_int_max ()
-	     : dir.prec < 0 ? 6 : dir.prec);
+	int maxprec = dir.prec[1] < 0 ? 6 : dir.prec[1];
 	res.range.max = format_floating_max (type, 'e', maxprec);
 
 	res.range.likely = res.range.min;
 
 	/* The unlikely maximum accounts for the longest multibyte
 	   decimal point character.  */
-	if (dir.prec != 0)
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
 	  res.range.unlikely = res.range.max + target_mb_len_max () -1;
 	else
 	  res.range.unlikely = res.range.max;
@@ -1553,25 +1608,22 @@  format_floating (const directive &dir)
 	   when precision is greater than zero, then the lower bound
 	   is 2 plus precision (plus flags).  */
 	HOST_WIDE_INT minprec = 0;
-	if (dir.prec == HOST_WIDE_INT_MIN)
-	  minprec = 0;
-	else if (dir.prec < 0)
-	  minprec = 6 + !radix /* decimal point */;
-	else if (dir.prec)
-	  minprec = dir.prec + !radix /* decimal point */;
+	if (dir.prec[0] < 0)
+	  minprec = dir.prec[1] < 0 ? 6 + !radix /* decimal point */ : 0;
+	else if (dir.prec[0])
+	  minprec = dir.prec[0] + !radix /* decimal point */;
 
 	res.range.min = flagmin + radix + minprec;
 
 	/* Compute the upper bound for -TYPE_MAX.  */
-	HOST_WIDE_INT maxprec
-	  = dir.prec == HOST_WIDE_INT_MIN ? target_int_max () : dir.prec;
-	res.range.max = format_floating_max (type, 'f', maxprec);
+	res.range.max = format_floating_max (type, 'f', dir.prec[1]);
 
 	res.range.likely = res.range.min;
 
 	/* The unlikely maximum accounts for the longest multibyte
 	   decimal point character.  */
-	if (dir.prec != 0)
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
 	  res.range.unlikely = res.range.max + target_mb_len_max () - 1;
 	break;
       }
@@ -1584,10 +1636,7 @@  format_floating (const directive &dir)
 	   the lower bound on the range of bytes (not counting flags
 	   or width) is 1.  */
 	res.range.min = flagmin;
-
-	HOST_WIDE_INT maxprec
-	  = dir.prec == HOST_WIDE_INT_MIN ? target_int_max () : dir.prec;
-	res.range.max = format_floating_max (type, 'g', maxprec);
+	res.range.max = format_floating_max (type, 'g', dir.prec[1]);
 	res.range.likely = res.range.max;
 
 	/* The unlikely maximum accounts for the longest multibyte
@@ -1602,13 +1651,12 @@  format_floating (const directive &dir)
 
   /* Bump up the byte counters if WIDTH is greater.  */
   res.adjust_for_width_or_precision (dir.width);
-
   return res;
 }
 
 /* Return a range representing the minimum and maximum number of bytes
-   that the conversion specification DIR will write on output for the
-   floating argument ARG.  */
+   that the directive DIR will write on output for the floating argument
+   ARG.  */
 
 static fmtresult
 format_floating (const directive &dir, tree arg)
@@ -1616,7 +1664,7 @@  format_floating (const directive &dir, tree arg)
   if (!arg || TREE_CODE (arg) != REAL_CST)
     return format_floating (dir);
 
-  HOST_WIDE_INT prec[] = { dir.prec, dir.prec };
+  HOST_WIDE_INT prec[] = { dir.prec[0], dir.prec[1] };
 
   if (TOUPPER (dir.specifier) == 'A')
     {
@@ -1715,7 +1763,6 @@  format_floating (const directive &dir, tree arg)
     }
 
   res.adjust_for_width_or_precision (dir.width);
-
   return res;
 }
 
@@ -1806,35 +1853,40 @@  format_character (const directive &dir, tree arg)
 
   if (dir.modifier == FMT_LEN_l)
     {
-      unsigned HOST_WIDE_INT val;
-
-      if (arg && TREE_CODE (arg) == INTEGER_CST && tree_fits_shwi_p (arg))
-	val = tree_to_shwi (arg);
-      else
-	val = HOST_WIDE_INT_MAX;
-
       /* A wide character can result in as few as zero bytes.  */
       res.range.min = 0;
 
-      if (val == 0)
-	{
-	  /* The NUL wide character results in no bytes.  */
-	  res.range.max = 0;
-	  res.range.likely = 0;
-	  res.range.unlikely = 0;
-	}
-      else if (0 < val && val < 128)
+      HOST_WIDE_INT min, max;
+      if (get_int_range (arg, integer_type_node, &min, &max, false, 0))
 	{
-	  /* A wide character in the ASCII range most likely results
-	     in a single byte, and only unlikely in up to MB_LEN_MAX.  */
-	  res.range.max = 1;
-	  res.range.likely = 1;
-	  res.range.unlikely = target_mb_len_max ();
+	  if (min == 0 && max == 0)
+	    {
+	      /* The NUL wide character results in no bytes.  */
+	      res.range.max = 0;
+	      res.range.likely = 0;
+	      res.range.unlikely = 0;
+	    }
+	  else if (0 < min && min < 128)
+	    {
+	      /* A wide character in the ASCII range most likely results
+		 in a single byte, and only unlikely in up to MB_LEN_MAX.  */
+	      res.range.max = 1;
+	      res.range.likely = 1;
+	      res.range.unlikely = target_mb_len_max ();
+	    }
+	  else
+	    {
+	      /* A wide character outside the ASCII range likely results
+		 in up to two bytes, and only unlikely in up to MB_LEN_MAX.  */
+	      res.range.max = target_mb_len_max ();
+	      res.range.likely = 2;
+	      res.range.unlikely = res.range.max;
+	    }
 	}
       else
 	{
-	  /* A wide character outside the ASCII range likely results
-	     in up to two bytes, and only unlikely in up to MB_LEN_MAX.  */
+	  /* An unknown wide character is treated the same as a wide
+	     character outside the ASCII range.  */
 	  res.range.max = target_mb_len_max ();
 	  res.range.likely = 2;
 	  res.range.unlikely = res.range.max;
@@ -1844,7 +1896,7 @@  format_character (const directive &dir, tree arg)
     {
       /* A plain '%c' directive.  Its ouput is exactly 1.  */
       res.range.min = res.range.max = 1;
-      res.range.likely = res.range.unlikely = res.range.min;
+      res.range.likely = res.range.unlikely = 1;
       res.knownrange = true;
     }
 
@@ -1853,9 +1905,9 @@  format_character (const directive &dir, tree arg)
 }
 
 /* Return the minimum and maximum number of characters formatted
-   by the '%c' and '%s' format directives and ther wide character
-   forms for the argument ARG.  ARG can be null (for functions
-   such as vsprinf).  */
+   by the '%s' format directive and its wide character form for
+   the argument ARG.  ARG can be null (for functions such as
+   vsprinf).  */
 
 static fmtresult
 format_string (const directive &dir, tree arg)
@@ -1883,18 +1935,19 @@  format_string (const directive &dir, tree arg)
 	     2 * wcslen (S).*/
 	  res.range.likely = res.range.min * 2;
 
-	  /* For a wide character string, use precision as the maximum
-	     even if precision is greater than the string length since
-	     the number of bytes the string converts to may be greater
-	     (due to MB_CUR_MAX).  */
-	  if (0 <= dir.prec
-	      && (unsigned HOST_WIDE_INT)dir.prec < res.range.max)
+	  if (0 <= dir.prec[1]
+	      && (unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)
 	    {
-	      res.range.max = dir.prec;
-	      res.range.likely = dir.prec;
-	      res.range.unlikely = dir.prec;
+	      res.range.max = dir.prec[1];
+	      res.range.likely = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
 	    }
 
+	  if (dir.prec[0] < 0 && dir.prec[1] > -1)
+	    res.range.min = 0;
+	  else if (0 <= dir.prec[0])
+	    res.range.likely = dir.prec[0];
+
 	  /* Even a non-empty wide character string need not convert into
 	     any bytes.  */
 	  res.range.min = 0;
@@ -1903,20 +1956,16 @@  format_string (const directive &dir, tree arg)
 	{
 	  res.knownrange = true;
 
-	  if (dir.prec == HOST_WIDE_INT_MIN)
+	  if (dir.prec[0] < 0 && dir.prec[1] > -1)
 	    res.range.min = 0;
-	  else if ((unsigned HOST_WIDE_INT)dir.prec < res.range.min)
-	    {
-	      res.range.min = dir.prec;
-	      res.range.max = dir.prec;
-	      res.range.likely = dir.prec;
-	      res.range.unlikely = dir.prec;
-	    }
-	  else if ((unsigned HOST_WIDE_INT)dir.prec < res.range.max)
+	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < res.range.min)
+	    res.range.min = dir.prec[0];
+
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)
 	    {
-	      res.range.max = dir.prec;
-	      res.range.likely = dir.prec;
-	      res.range.unlikely = dir.prec;
+	      res.range.max = dir.prec[1];
+	      res.range.likely = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
 	    }
 	}
     }
@@ -1936,20 +1985,20 @@  format_string (const directive &dir, tree arg)
 	 in mode 2, and the maximum is PRECISION or -1 to disable
 	 tracking.  */
 
-      if (0 <= dir.prec)
+      if (0 <= dir.prec[0])
 	{
 	  if (slen.range.min >= target_int_max ())
 	    slen.range.min = 0;
-	  else if ((unsigned HOST_WIDE_INT)dir.prec < slen.range.min)
+	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < slen.range.min)
 	    {
-	      slen.range.min = dir.prec;
+	      slen.range.min = dir.prec[0];
 	      slen.range.likely = slen.range.min;
 	    }
 
-	  if ((unsigned HOST_WIDE_INT)dir.prec < slen.range.max
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max
 	      || slen.range.max >= target_int_max ())
 	    {
-	      slen.range.max = dir.prec;
+	      slen.range.max = dir.prec[1];
 	      slen.range.likely = slen.range.max;
 	    }
 	}
@@ -2348,35 +2397,32 @@  maybe_warn (substring_loc &dirloc, source_range *pargrange,
 		  avail_range.min, avail_range.max);
 }
 
-/* Compute the length of the output resulting from the conversion
-   specification DIR with the argument ARG in a call described by INFO
-   and update the overall result of the call in *RES.  The format directive
-   corresponding to DIR starts at CVTBEG and is CVTLEN characters long.  */
+/* Compute the length of the output resulting from the directive DIR
+   in a call described by INFO and update the overall result of the call
+   in *RES.  Return true if the directive has been handled.  */
 
 static bool
 format_directive (const pass_sprintf_length::call_info &info,
 		  format_result *res, const directive &dir)
 {
-  const char *cvtbeg = dir.beg;
-  size_t cvtlen = dir.len;
-  tree arg = dir.arg;
-
   /* Offset of the beginning of the directive from the beginning
      of the format string.  */
-  size_t offset = cvtbeg - info.fmtstr;
+  size_t offset = dir.beg - info.fmtstr;
+  size_t start = offset;
+  size_t length = offset + dir.len - !!dir.len;
 
   /* Create a location for the whole directive from the % to the format
      specifier.  */
   substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
-			offset, offset, offset + cvtlen - 1);
+			offset, start, length);
 
   /* Also create a location range for the argument if possible.
      This doesn't work for integer literals or function calls.  */
   source_range argrange;
   source_range *pargrange;
-  if (arg && CAN_HAVE_LOCATION_P (arg))
+  if (dir.arg && CAN_HAVE_LOCATION_P (dir.arg))
     {
-      argrange = EXPR_LOCATION_RANGE (arg);
+      argrange = EXPR_LOCATION_RANGE (dir.arg);
       pargrange = &argrange;
     }
   else
@@ -2387,8 +2433,8 @@  format_directive (const pass_sprintf_length::call_info &info,
   if (!dir.fmtfunc || res->range.min >= HOST_WIDE_INT_MAX)
     return false;
 
-  /* Compute the (approximate) length of the formatted output.  */
-  fmtresult fmtres = dir.fmtfunc (dir, arg);
+  /* Compute the range of lengths of the formatted output.  */
+  fmtresult fmtres = dir.fmtfunc (dir, dir.arg);
 
   /* Record whether the output of all directives is known to be
      bounded by some maximum, implying that their arguments are
@@ -2516,7 +2562,6 @@  format_directive (const pass_sprintf_length::call_info &info,
 	}
     }
 
-  /* Has the minimum directive output length exceeded INT_MAX?  */
   /* Has the likely and maximum directive output exceeded INT_MAX?  */
   bool likelyximax = *dir.beg && res->range.likely > target_int_max ();
   bool maxximax = *dir.beg && res->range.max > target_int_max ();
@@ -2939,7 +2984,8 @@  parse_directive (pass_sprintf_length::call_info &info,
 	{
 	  /* Width specified by a va_list takes on the range [0, -INT_MIN]
 	     (width is the absolute value of that specified).  */
-	  dir.width = HOST_WIDE_INT_MIN;
+	  dir.width[0] = 0;
+	  dir.width[1] = target_int_max () + 1;
 	}
     }
   else
@@ -2953,7 +2999,8 @@  parse_directive (pass_sprintf_length::call_info &info,
 	{
 	  /* Precision specified by a va_list takes on the range [-1, INT_MAX]
 	     (unlike width, negative precision is ignored).  */
-	  dir.prec = HOST_WIDE_INT_MIN;
+	  dir.prec[0] = -1;
+	  dir.prec[1] = target_int_max ();
 	}
     }
   else
@@ -2976,11 +3023,22 @@  parse_directive (pass_sprintf_length::call_info &info,
 	       dir.dirno, (size_t)(dir.beg - info.fmtstr),
 	       (int)dir.len, dir.beg);
       if (star_width)
-	fprintf (dump_file, ", width = %lli", (long long)dir.width);
+	{
+	  if (dir.width[0] == dir.width[1])
+	    fprintf (dump_file, ", width = %lli", (long long)dir.width[0]);
+	  else
+	    fprintf (dump_file, ", width in range [%lli, %lli]",
+		     (long long)dir.width[0], (long long)dir.width[1]);
+	}
 
       if (star_precision)
-	fprintf (dump_file, ", precision = %lli", (long long)dir.prec);
-
+	{
+	  if (dir.prec[0] == dir.prec[1])
+	    fprintf (dump_file, ", precision = %lli", (long long)dir.prec[0]);
+	  else
+	    fprintf (dump_file, ", precision in range [%lli, %lli]",
+		     (long long)dir.prec[0], (long long)dir.prec[1]);
+	}
       fputc ('\n', dump_file);
     }
 
@@ -3009,7 +3067,7 @@  pass_sprintf_length::compute_format_length (call_info &info,
 	       (unsigned long long)info.objsize, info.fmtstr);
     }
 
-  /* Reset the minimum and maximum bytes counters.  */
+  /* Reset the minimum and maximum byte counters.  */
   res->range.min = res->range.max = 0;
 
   /* No directive has been seen yet so the length of output is bounded
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c
index cc226ca..3a6586b 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-warn-1.c
@@ -4,6 +4,9 @@ 
 typedef struct
 {
   char a0[0];
+  /* Separate a0 from a1 to prevent the former from being substituted
+     for the latter and causing false positives.  */
+  int: 8;
   char a1[1];
   char a2[2];
   char a3[3];
@@ -23,11 +26,13 @@  int value_range (int min, int max)
 
 #define R(min, max)  value_range (min, max)
 
+extern void sink (void*);
+
 /* Verify that calls to snprintf whose return value is unused are
    diagnosed if certain or possible truncation is detected.  */
 
 #define T(size, ...) \
-  __builtin_snprintf (buffer (size), size, __VA_ARGS__)
+  __builtin_snprintf (buffer (size), size, __VA_ARGS__), sink (buffer)
 
 void test_int_retval_unused (void)
 {
@@ -39,9 +44,20 @@  void test_int_retval_unused (void)
 
 void test_string_retval_unused (const Arrays *ar)
 {
+  /* At level 1 strings of unknown length are assumed to be empty so
+     the following is not diagnosed.  */
   T (1, "%-s", ar->a0);
+  /* A one-byte array can only hold an empty string, so the following
+     isn't diagnosed.  */
   T (1, "%-s", ar->a1);
-  T (1, "%-s", ar->a2);   /* { dg-warning "output may be truncated" } */
+  /* Unlike the ar->a0 case above, at level 1, the length of an unknown
+     string that points to an array of known size is assumed to be the
+     size of the array minus 1.  */
+  T (1, "%-s", ar->a2);      /* { dg-warning "output may be truncated" } */
+  T (1, "%-s", ar->a3);      /* { dg-warning "output may be truncated" } */
+  T (1, "%-s", ar->a4);      /* { dg-warning "output may be truncated" } */
+  /* Same as the ar->a0 case above.  */
+  T (1, "%-s", ar->ax);
 }
 
 
@@ -68,6 +84,7 @@  void test_string_retval_used (const Arrays *ar)
   T (1, "%-s", ar->a0);
   T (1, "%-s", ar->a1);
   T (1, "%-s", ar->a2);
+  T (1, "%-s", ar->a3);
   T (1, "%-s", ar->a4);
   T (1, "%-s", "123");   /* { dg-warning "output truncated" } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-10.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-10.c
new file mode 100644
index 0000000..5523acd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-10.c
@@ -0,0 +1,270 @@ 
+/* 78696 - -fprintf-return-value misoptimizes %.Ng where N is greater than 10
+   Test to verify the correctness of ranges of output computed for floating
+   point directives.
+   { dg-do compile }
+   { dg-options "-O2 -Wformat -Wformat-overflow -ftrack-macro-expansion=0" } */
+
+typedef __builtin_va_list va_list;
+
+char dst[1];
+
+extern void sink (int, void*);
+
+/* Macro to test either width or precision specified by the asterisk
+   (but not both).  */
+#define T1(fmt, a)    sink (__builtin_sprintf (dst + 1, fmt, a, x), dst)
+
+/* Macro to test both width and precision specified by the asterisk.  */
+#define T2(fmt, w, p) sink (__builtin_sprintf (dst + 1, fmt, w, p, x), dst)
+
+/* Macro to test vsprintf with both width and precision specified by
+   the asterisk.  */
+#define T(fmt) sink (__builtin_vsprintf (dst + 1, fmt, va), dst)
+
+/* Exercise %a.  */
+void test_a (int w, int p, double x)
+{
+  T1 ("%.*a", 0);     /* { dg-warning "between 6 and 10 bytes" } */
+  T1 ("%.*a", 1);     /* { dg-warning "between 8 and 12 bytes" } */
+  T1 ("%.*a", 2);     /* { dg-warning "between 9 and 13 bytes" } */
+  T1 ("%.*a", 99);    /* { dg-warning "between 106 and 110 bytes" } */
+  T1 ("%.*a", 199);   /* { dg-warning "between 206 and 210 bytes" } */
+  T1 ("%.*a", 1099);  /* { dg-warning "between 1106 and 1110 bytes" } */
+
+  T1 ("%*.a", 0);     /* { dg-warning "between 6 and 10 bytes" } */
+  T1 ("%*.a", 1);     /* { dg-warning "between 6 and 10 bytes" } */
+  T1 ("%*.a", 3);     /* { dg-warning "between 6 and 10 bytes" } */
+  T1 ("%*.a", 6);     /* { dg-warning "between 6 and 10 bytes" } */
+  T1 ("%*.a", 7);     /* { dg-warning "between 7 and 10 bytes" } */
+
+  T1 ("%*.a", w);     /* { dg-warning "writing between 6 and 2147483648 bytes" } */
+  T1 ("%*.0a", w);    /* { dg-warning "writing between 6 and 2147483648 bytes" } */
+  T1 ("%*.1a", w);    /* { dg-warning "writing between 8 and 2147483648 bytes" } */
+  T1 ("%*.2a", w);    /* { dg-warning "writing between 9 and 2147483648 bytes" } */
+
+  T1 ("%.*a",  p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T1 ("%1.*a", p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T1 ("%2.*a", p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T1 ("%3.*a", p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+
+  T2 ("%*.*a", w, p); /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+}
+
+/* Exercise %e.  */
+void test_e (int w, int p, double x)
+{
+  T1 ("%.*e", 0);     /* { dg-warning "between 5 and 7 bytes" } */
+  T1 ("%.*e", 1);     /* { dg-warning "between 7 and 9 bytes" } */
+  T1 ("%.*e", 2);     /* { dg-warning "between 8 and 10 bytes" } */
+  T1 ("%.*e", 99);    /* { dg-warning "between 105 and 107 bytes" } */
+  T1 ("%.*e", 199);   /* { dg-warning "between 205 and 207 bytes" } */
+  T1 ("%.*e", 1099);  /* { dg-warning "between 1105 and 1107 bytes" } */
+
+  T1 ("%*.e", 0);     /* { dg-warning "between 5 and 7 bytes" } */
+  T1 ("%*.e", 1);     /* { dg-warning "between 5 and 7 bytes" } */
+  T1 ("%*.e", 1);     /* { dg-warning "between 5 and 7 bytes" } */
+  T1 ("%*.e", 3);     /* { dg-warning "between 5 and 7 bytes" } */
+  T1 ("%*.e", 6);     /* { dg-warning "between 6 and 7 bytes" } */
+  T1 ("%*.e", 7);     /* { dg-warning "writing 7 bytes" } */
+
+  T1 ("%*.e", w);     /* { dg-warning "writing between 5 and 2147483648 bytes" } */
+  T1 ("%*.0e", w);    /* { dg-warning "writing between 5 and 2147483648 bytes" } */
+  T1 ("%*.1e", w);    /* { dg-warning "writing between 7 and 2147483648 bytes" } */
+  T1 ("%*.2e", w);    /* { dg-warning "writing between 8 and 2147483648 bytes" } */
+
+  T1 ("%.*e",  p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+  T1 ("%1.*e", p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+  T1 ("%2.*e", p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+  T1 ("%3.*e", p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+
+  T2 ("%*.*e", w, p); /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+}
+
+/* Exercise %f.  */
+void test_f (int w, int p, double x)
+{
+  T1 ("%.*f", 0);           /* { dg-warning "between 1 and 310 bytes" } */
+  T1 ("%.*f", 1);           /* { dg-warning "between 3 and 312 bytes" } */
+  T1 ("%.*f", 2);           /* { dg-warning "between 4 and 313 bytes" } */
+  T1 ("%.*f", 99);          /* { dg-warning "between 101 and 410 bytes" } */
+  T1 ("%.*f", 199);         /* { dg-warning "between 201 and 510 bytes" } */
+  T1 ("%.*f", 1099);        /* { dg-warning "between 1101 and 1410 bytes" } */
+
+  T2 ("%*.*f", 0, 0);       /* { dg-warning "between 1 and 310 bytes" } */
+  T2 ("%*.*f", 1, 0);       /* { dg-warning "between 1 and 310 bytes" } */
+  T2 ("%*.*f", 2, 0);       /* { dg-warning "between 2 and 310 bytes" } */
+  T2 ("%*.*f", 3, 0);       /* { dg-warning "between 3 and 310 bytes" } */
+  T2 ("%*.*f", 310, 0);     /* { dg-warning "writing 310 bytes" } */
+  T2 ("%*.*f", 311, 0);     /* { dg-warning "writing 311 bytes" } */
+  T2 ("%*.*f", 312, 312);   /* { dg-warning "between 314 and 623 bytes" } */
+  T2 ("%*.*f", 312, 313);   /* { dg-warning "between 315 and 624 bytes" } */
+
+  T1 ("%*.f", w);           /* { dg-warning "writing between 1 and 2147483648 bytes" } */
+  T1 ("%*.0f", w);          /* { dg-warning "writing between 1 and 2147483648 bytes" } */
+  T1 ("%*.1f", w);          /* { dg-warning "writing between 3 and 2147483648 bytes" } */
+  T1 ("%*.2f", w);          /* { dg-warning "writing between 4 and 2147483648 bytes" } */
+
+  T1 ("%.*f",  p);          /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+  T1 ("%1.*f", p);          /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+  T1 ("%2.*f", p);          /* { dg-warning "writing between 2 and 2147483958 bytes" } */
+  T1 ("%3.*f", p);          /* { dg-warning "writing between 3 and 2147483958 bytes" } */
+
+  T2 ("%*.*f", w, p);       /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+}
+
+/* Exercise %g.  The expected output is the lesser of %e and %f.  */
+void test_g (double x)
+{
+  T1 ("%.*g", 0);           /* { dg-warning "between 1 and 7 bytes" } */
+  T1 ("%.*g", 1);           /* { dg-warning "between 1 and 7 bytes" } */
+  T1 ("%.*g", 2);           /* { dg-warning "between 1 and 9 bytes" } */
+  T1 ("%.*g", 99);          /* { dg-warning "between 1 and 106 bytes" } */
+  T1 ("%.*g", 199);         /* { dg-warning "between 1 and 206 bytes" } */
+  T1 ("%.*g", 1099);        /* { dg-warning "between 1 and 310 bytes" } */
+
+  T2 ("%*.*g", 0, 0);       /* { dg-warning "between 1 and 7 bytes" } */
+  T2 ("%*.*g", 1, 0);       /* { dg-warning "between 1 and 7 bytes" } */
+  T2 ("%*.*g", 2, 0);       /* { dg-warning "between 2 and 7 bytes" } */
+  T2 ("%*.*g", 3, 0);       /* { dg-warning "between 3 and 7 bytes" } */
+  T2 ("%*.*g", 7, 0);       /* { dg-warning "writing 7 bytes" } */
+  T2 ("%*.*g", 310, 0);     /* { dg-warning "writing 310 bytes" } */
+  T2 ("%*.*g", 311, 0);     /* { dg-warning "writing 311 bytes" } */
+  T2 ("%*.*g", 312, 312);   /* { dg-warning "writing 312 bytes" } */
+  T2 ("%*.*g", 312, 313);   /* { dg-warning "writing 312 bytes" } */
+  T2 ("%*.*g", 333, 999);   /* { dg-warning "writing 333 bytes" } */
+}
+
+/* Exercise %a.  */
+void test_a_va (va_list va)
+{
+  T ("%.0a");       /* { dg-warning "between 6 and 10 bytes" } */
+  T ("%.1a");       /* { dg-warning "between 8 and 12 bytes" } */
+  T ("%.2a");       /* { dg-warning "between 9 and 13 bytes" } */
+  T ("%.99a");      /* { dg-warning "between 106 and 110 bytes" } */
+  T ("%.199a");     /* { dg-warning "between 206 and 210 bytes" } */
+  T ("%.1099a");    /* { dg-warning "between 1106 and 1110 bytes" } */
+
+  T ("%0.a");       /* { dg-warning "between 6 and 10 bytes" } */
+  T ("%1.a");       /* { dg-warning "between 6 and 10 bytes" } */
+  T ("%3.a");       /* { dg-warning "between 6 and 10 bytes" } */
+  T ("%6.a");       /* { dg-warning "between 6 and 10 bytes" } */
+  T ("%7.a");       /* { dg-warning "between 7 and 10 bytes" } */
+
+  T ("%*.a");       /* { dg-warning "writing between 6 and 2147483648 bytes" } */
+  T ("%*.0a");      /* { dg-warning "writing between 6 and 2147483648 bytes" } */
+  T ("%*.1a");      /* { dg-warning "writing between 8 and 2147483648 bytes" } */
+  T ("%*.2a");      /* { dg-warning "writing between 9 and 2147483648 bytes" } */
+
+  T ("%.*a");       /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T ("%1.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T ("%2.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T ("%6.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+  T ("%9.*a");      /* { dg-warning "writing between 9 and 2147483658 bytes" } */
+
+  T ("%*.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
+}
+
+/* Exercise %e.  */
+void test_e_va (va_list va)
+{
+  T ("%e");         /* { dg-warning "between 12 and 14 bytes" } */
+  T ("%+e");        /* { dg-warning "between 13 and 14 bytes" } */
+  T ("% e");        /* { dg-warning "between 13 and 14 bytes" } */
+  T ("%#e");        /* { dg-warning "between 12 and 14 bytes" } */
+  T ("%#+e");       /* { dg-warning "between 13 and 14 bytes" } */
+  T ("%# e");       /* { dg-warning "between 13 and 14 bytes" } */
+
+  T ("%.e");        /* { dg-warning "between 5 and 7 bytes" } */
+  T ("%.0e");       /* { dg-warning "between 5 and 7 bytes" } */
+  T ("%.1e");       /* { dg-warning "between 7 and 9 bytes" } */
+  T ("%.2e");       /* { dg-warning "between 8 and 10 bytes" } */
+  T ("%.99e");      /* { dg-warning "between 105 and 107 bytes" } */
+  T ("%.199e");     /* { dg-warning "between 205 and 207 bytes" } */
+  T ("%.1099e");    /* { dg-warning "between 1105 and 1107 bytes" } */
+
+  T ("%0.e");       /* { dg-warning "between 5 and 7 bytes" } */
+  T ("%1.e");       /* { dg-warning "between 5 and 7 bytes" } */
+  T ("%1.e");       /* { dg-warning "between 5 and 7 bytes" } */
+  T ("%3.e");       /* { dg-warning "between 5 and 7 bytes" } */
+  T ("%6.e");       /* { dg-warning "between 6 and 7 bytes" } */
+  T ("%7.e");       /* { dg-warning "writing 7 bytes" } */
+
+  T ("%.*e");       /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+  T ("%1.*e");      /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+  T ("%6.*e");      /* { dg-warning "writing between 6 and 2147483655 bytes" } */
+  T ("%9.*e");      /* { dg-warning "writing between 9 and 2147483655 bytes" } */
+
+  T ("%*.*e");      /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+}
+
+/* Exercise %f.  */
+void test_f_va (va_list va)
+{
+  T ("%f");         /* { dg-warning "between 8 and 317 bytes" } */
+  T ("%+f");        /* { dg-warning "between 9 and 317 bytes" } */
+  T ("% f");        /* { dg-warning "between 9 and 317 bytes" } */
+  T ("%#f");        /* { dg-warning "between 8 and 317 bytes" } */
+  T ("%+f");        /* { dg-warning "between 9 and 317 bytes" } */
+  T ("% f");        /* { dg-warning "between 9 and 317 bytes" } */
+  T ("%#+f");       /* { dg-warning "between 9 and 317 bytes" } */
+  T ("%# f");       /* { dg-warning "between 9 and 317 bytes" } */
+
+  T ("%.f");        /* { dg-warning "between 1 and 310 bytes" } */
+  T ("%.0f");       /* { dg-warning "between 1 and 310 bytes" } */
+  T ("%.1f");       /* { dg-warning "between 3 and 312 bytes" } */
+  T ("%.2f");       /* { dg-warning "between 4 and 313 bytes" } */
+  T ("%.99f");      /* { dg-warning "between 101 and 410 bytes" } */
+  T ("%.199f");     /* { dg-warning "between 201 and 510 bytes" } */
+  T ("%.1099f");    /* { dg-warning "between 1101 and 1410 bytes" } */
+
+  T ("%0.0f");      /* { dg-warning "between 1 and 310 bytes" } */
+  T ("%1.0f");      /* { dg-warning "between 1 and 310 bytes" } */
+  T ("%2.0f");      /* { dg-warning "between 2 and 310 bytes" } */
+  T ("%3.0f");      /* { dg-warning "between 3 and 310 bytes" } */
+  T ("%310.0f");    /* { dg-warning "writing 310 bytes" } */
+  T ("%311.0f");    /* { dg-warning "writing 311 bytes" } */
+  T ("%312.312f");  /* { dg-warning "between 314 and 623 bytes" } */
+  T ("%312.313f");  /* { dg-warning "between 315 and 624 bytes" } */
+
+  T ("%.*f");       /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+  T ("%1.*f");      /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+  T ("%3.*f");      /* { dg-warning "writing between 3 and 2147483958 bytes" } */
+
+  T ("%*.*f");      /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+}
+
+/* Exercise %g.  The expected output is the lesser of %e and %f.  */
+void test_g_va (va_list va)
+{
+  T ("%g");         /* { dg-warning "between 1 and 13 bytes" } */
+  T ("%+g");        /* { dg-warning "between 2 and 13 bytes" } */
+  T ("% g");        /* { dg-warning "between 2 and 13 bytes" } */
+  T ("%#g");        /* { dg-warning "between 1 and 13 bytes" } */
+  T ("%#+g");       /* { dg-warning "between 2 and 13 bytes" } */
+  T ("%# g");       /* { dg-warning "between 2 and 13 bytes" } */
+
+  T ("%.g");        /* { dg-warning "between 1 and 7 bytes" } */
+  T ("%.0g");       /* { dg-warning "between 1 and 7 bytes" } */
+  T ("%.1g");       /* { dg-warning "between 1 and 7 bytes" } */
+  T ("%.2g");       /* { dg-warning "between 1 and 9 bytes" } */
+  T ("%.99g");      /* { dg-warning "between 1 and 106 bytes" } */
+  T ("%.199g");     /* { dg-warning "between 1 and 206 bytes" } */
+  T ("%.1099g");    /* { dg-warning "between 1 and 310 bytes" } */
+
+  T ("%0.0g");      /* { dg-warning "between 1 and 7 bytes" } */
+  T ("%1.0g");      /* { dg-warning "between 1 and 7 bytes" } */
+  T ("%2.0g");      /* { dg-warning "between 2 and 7 bytes" } */
+  T ("%3.0g");      /* { dg-warning "between 3 and 7 bytes" } */
+  T ("%7.0g");      /* { dg-warning "writing 7 bytes" } */
+  T ("%310.0g");    /* { dg-warning "writing 310 bytes" } */
+  T ("%311.0g");    /* { dg-warning "writing 311 bytes" } */
+  T ("%312.312g");  /* { dg-warning "writing 312 bytes" } */
+  T ("%312.313g");  /* { dg-warning "writing 312 bytes" } */
+  T ("%333.999g");  /* { dg-warning "writing 333 bytes" } */
+
+  T ("%.*g");       /* { dg-warning "writing between 1 and 310 bytes" } */
+  T ("%1.*g");      /* { dg-warning "writing between 1 and 310 bytes" } */
+  T ("%4.*g");      /* { dg-warning "writing between 4 and 310 bytes" } */
+
+  T ("%*.*g");      /* { dg-warning "writing between 1 and 2147483648 bytes" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
index 5523acd..59d2927 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
@@ -1,270 +1,158 @@ 
-/* 78696 - -fprintf-return-value misoptimizes %.Ng where N is greater than 10
-   Test to verify the correctness of ranges of output computed for floating
-   point directives.
-   { dg-do compile }
-   { dg-options "-O2 -Wformat -Wformat-overflow -ftrack-macro-expansion=0" } */
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wformat -Wformat-overflow=2 -ftrack-macro-expansion=0" } */
 
-typedef __builtin_va_list va_list;
+typedef __SIZE_TYPE__ size_t;
 
-char dst[1];
+#define INT_MAX   __INT_MAX__
+#define INT_MIN   (-INT_MAX - 1)
 
-extern void sink (int, void*);
+#ifndef LINE
+#  define LINE 0
+#endif
 
-/* Macro to test either width or precision specified by the asterisk
-   (but not both).  */
-#define T1(fmt, a)    sink (__builtin_sprintf (dst + 1, fmt, a, x), dst)
+int dummy_sprintf (char*, const char*, ...);
+void sink (void*);
 
-/* Macro to test both width and precision specified by the asterisk.  */
-#define T2(fmt, w, p) sink (__builtin_sprintf (dst + 1, fmt, w, p, x), dst)
+char buffer[4096];
+char *ptr;
 
-/* Macro to test vsprintf with both width and precision specified by
-   the asterisk.  */
-#define T(fmt) sink (__builtin_vsprintf (dst + 1, fmt, va), dst)
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define FUNC(f)							\
+  ((!LINE || LINE == __LINE__) ? __builtin_ ## f : dummy_ ## f)
 
-/* Exercise %a.  */
-void test_a (int w, int p, double x)
-{
-  T1 ("%.*a", 0);     /* { dg-warning "between 6 and 10 bytes" } */
-  T1 ("%.*a", 1);     /* { dg-warning "between 8 and 12 bytes" } */
-  T1 ("%.*a", 2);     /* { dg-warning "between 9 and 13 bytes" } */
-  T1 ("%.*a", 99);    /* { dg-warning "between 106 and 110 bytes" } */
-  T1 ("%.*a", 199);   /* { dg-warning "between 206 and 210 bytes" } */
-  T1 ("%.*a", 1099);  /* { dg-warning "between 1106 and 1110 bytes" } */
-
-  T1 ("%*.a", 0);     /* { dg-warning "between 6 and 10 bytes" } */
-  T1 ("%*.a", 1);     /* { dg-warning "between 6 and 10 bytes" } */
-  T1 ("%*.a", 3);     /* { dg-warning "between 6 and 10 bytes" } */
-  T1 ("%*.a", 6);     /* { dg-warning "between 6 and 10 bytes" } */
-  T1 ("%*.a", 7);     /* { dg-warning "between 7 and 10 bytes" } */
-
-  T1 ("%*.a", w);     /* { dg-warning "writing between 6 and 2147483648 bytes" } */
-  T1 ("%*.0a", w);    /* { dg-warning "writing between 6 and 2147483648 bytes" } */
-  T1 ("%*.1a", w);    /* { dg-warning "writing between 8 and 2147483648 bytes" } */
-  T1 ("%*.2a", w);    /* { dg-warning "writing between 9 and 2147483648 bytes" } */
-
-  T1 ("%.*a",  p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T1 ("%1.*a", p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T1 ("%2.*a", p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T1 ("%3.*a", p);    /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-
-  T2 ("%*.*a", w, p); /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-}
+/* Evaluate to an array of SIZE characters when non-negative, or to
+   a pointer to an unknown object otherwise.  */
+#define buffer(size)					\
+  ((0 <= size) ? buffer + sizeof buffer - (size) : ptr)
 
-/* Exercise %e.  */
-void test_e (int w, int p, double x)
-{
-  T1 ("%.*e", 0);     /* { dg-warning "between 5 and 7 bytes" } */
-  T1 ("%.*e", 1);     /* { dg-warning "between 7 and 9 bytes" } */
-  T1 ("%.*e", 2);     /* { dg-warning "between 8 and 10 bytes" } */
-  T1 ("%.*e", 99);    /* { dg-warning "between 105 and 107 bytes" } */
-  T1 ("%.*e", 199);   /* { dg-warning "between 205 and 207 bytes" } */
-  T1 ("%.*e", 1099);  /* { dg-warning "between 1105 and 1107 bytes" } */
-
-  T1 ("%*.e", 0);     /* { dg-warning "between 5 and 7 bytes" } */
-  T1 ("%*.e", 1);     /* { dg-warning "between 5 and 7 bytes" } */
-  T1 ("%*.e", 1);     /* { dg-warning "between 5 and 7 bytes" } */
-  T1 ("%*.e", 3);     /* { dg-warning "between 5 and 7 bytes" } */
-  T1 ("%*.e", 6);     /* { dg-warning "between 6 and 7 bytes" } */
-  T1 ("%*.e", 7);     /* { dg-warning "writing 7 bytes" } */
-
-  T1 ("%*.e", w);     /* { dg-warning "writing between 5 and 2147483648 bytes" } */
-  T1 ("%*.0e", w);    /* { dg-warning "writing between 5 and 2147483648 bytes" } */
-  T1 ("%*.1e", w);    /* { dg-warning "writing between 7 and 2147483648 bytes" } */
-  T1 ("%*.2e", w);    /* { dg-warning "writing between 8 and 2147483648 bytes" } */
-
-  T1 ("%.*e",  p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-  T1 ("%1.*e", p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-  T1 ("%2.*e", p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-  T1 ("%3.*e", p);    /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-
-  T2 ("%*.*e", w, p); /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-}
+#define T(bufsize, fmt, ...)						\
+  do {									\
+    char *buf = buffer (bufsize);					\
+    FUNC (sprintf)(buf, fmt, __VA_ARGS__);				\
+    sink (buf);								\
+  } while (0)
 
-/* Exercise %f.  */
-void test_f (int w, int p, double x)
-{
-  T1 ("%.*f", 0);           /* { dg-warning "between 1 and 310 bytes" } */
-  T1 ("%.*f", 1);           /* { dg-warning "between 3 and 312 bytes" } */
-  T1 ("%.*f", 2);           /* { dg-warning "between 4 and 313 bytes" } */
-  T1 ("%.*f", 99);          /* { dg-warning "between 101 and 410 bytes" } */
-  T1 ("%.*f", 199);         /* { dg-warning "between 201 and 510 bytes" } */
-  T1 ("%.*f", 1099);        /* { dg-warning "between 1101 and 1410 bytes" } */
-
-  T2 ("%*.*f", 0, 0);       /* { dg-warning "between 1 and 310 bytes" } */
-  T2 ("%*.*f", 1, 0);       /* { dg-warning "between 1 and 310 bytes" } */
-  T2 ("%*.*f", 2, 0);       /* { dg-warning "between 2 and 310 bytes" } */
-  T2 ("%*.*f", 3, 0);       /* { dg-warning "between 3 and 310 bytes" } */
-  T2 ("%*.*f", 310, 0);     /* { dg-warning "writing 310 bytes" } */
-  T2 ("%*.*f", 311, 0);     /* { dg-warning "writing 311 bytes" } */
-  T2 ("%*.*f", 312, 312);   /* { dg-warning "between 314 and 623 bytes" } */
-  T2 ("%*.*f", 312, 313);   /* { dg-warning "between 315 and 624 bytes" } */
-
-  T1 ("%*.f", w);           /* { dg-warning "writing between 1 and 2147483648 bytes" } */
-  T1 ("%*.0f", w);          /* { dg-warning "writing between 1 and 2147483648 bytes" } */
-  T1 ("%*.1f", w);          /* { dg-warning "writing between 3 and 2147483648 bytes" } */
-  T1 ("%*.2f", w);          /* { dg-warning "writing between 4 and 2147483648 bytes" } */
-
-  T1 ("%.*f",  p);          /* { dg-warning "writing between 1 and 2147483958 bytes" } */
-  T1 ("%1.*f", p);          /* { dg-warning "writing between 1 and 2147483958 bytes" } */
-  T1 ("%2.*f", p);          /* { dg-warning "writing between 2 and 2147483958 bytes" } */
-  T1 ("%3.*f", p);          /* { dg-warning "writing between 3 and 2147483958 bytes" } */
-
-  T2 ("%*.*f", w, p);       /* { dg-warning "writing between 1 and 2147483958 bytes" } */
-}
 
-/* Exercise %g.  The expected output is the lesser of %e and %f.  */
-void test_g (double x)
-{
-  T1 ("%.*g", 0);           /* { dg-warning "between 1 and 7 bytes" } */
-  T1 ("%.*g", 1);           /* { dg-warning "between 1 and 7 bytes" } */
-  T1 ("%.*g", 2);           /* { dg-warning "between 1 and 9 bytes" } */
-  T1 ("%.*g", 99);          /* { dg-warning "between 1 and 106 bytes" } */
-  T1 ("%.*g", 199);         /* { dg-warning "between 1 and 206 bytes" } */
-  T1 ("%.*g", 1099);        /* { dg-warning "between 1 and 310 bytes" } */
-
-  T2 ("%*.*g", 0, 0);       /* { dg-warning "between 1 and 7 bytes" } */
-  T2 ("%*.*g", 1, 0);       /* { dg-warning "between 1 and 7 bytes" } */
-  T2 ("%*.*g", 2, 0);       /* { dg-warning "between 2 and 7 bytes" } */
-  T2 ("%*.*g", 3, 0);       /* { dg-warning "between 3 and 7 bytes" } */
-  T2 ("%*.*g", 7, 0);       /* { dg-warning "writing 7 bytes" } */
-  T2 ("%*.*g", 310, 0);     /* { dg-warning "writing 310 bytes" } */
-  T2 ("%*.*g", 311, 0);     /* { dg-warning "writing 311 bytes" } */
-  T2 ("%*.*g", 312, 312);   /* { dg-warning "writing 312 bytes" } */
-  T2 ("%*.*g", 312, 313);   /* { dg-warning "writing 312 bytes" } */
-  T2 ("%*.*g", 333, 999);   /* { dg-warning "writing 333 bytes" } */
-}
+/* Identity function to verify that the checker figures out the value
+   of the operand even when it's not constant (i.e., makes use of
+   inlining and constant propagation information).  */
 
-/* Exercise %a.  */
-void test_a_va (va_list va)
-{
-  T ("%.0a");       /* { dg-warning "between 6 and 10 bytes" } */
-  T ("%.1a");       /* { dg-warning "between 8 and 12 bytes" } */
-  T ("%.2a");       /* { dg-warning "between 9 and 13 bytes" } */
-  T ("%.99a");      /* { dg-warning "between 106 and 110 bytes" } */
-  T ("%.199a");     /* { dg-warning "between 206 and 210 bytes" } */
-  T ("%.1099a");    /* { dg-warning "between 1106 and 1110 bytes" } */
-
-  T ("%0.a");       /* { dg-warning "between 6 and 10 bytes" } */
-  T ("%1.a");       /* { dg-warning "between 6 and 10 bytes" } */
-  T ("%3.a");       /* { dg-warning "between 6 and 10 bytes" } */
-  T ("%6.a");       /* { dg-warning "between 6 and 10 bytes" } */
-  T ("%7.a");       /* { dg-warning "between 7 and 10 bytes" } */
-
-  T ("%*.a");       /* { dg-warning "writing between 6 and 2147483648 bytes" } */
-  T ("%*.0a");      /* { dg-warning "writing between 6 and 2147483648 bytes" } */
-  T ("%*.1a");      /* { dg-warning "writing between 8 and 2147483648 bytes" } */
-  T ("%*.2a");      /* { dg-warning "writing between 9 and 2147483648 bytes" } */
-
-  T ("%.*a");       /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T ("%1.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T ("%2.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T ("%6.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-  T ("%9.*a");      /* { dg-warning "writing between 9 and 2147483658 bytes" } */
-
-  T ("%*.*a");      /* { dg-warning "writing between 6 and 2147483658 bytes" } */
-}
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+/* Function to "generate" a unique unknown number (as far as GCC can
+   tell) each time it's called.  It prevents the optimizer from being
+   able to narrow down the ranges of possible values in test functions
+   with repeated references to the same variable.  */
+extern int value (void);
 
-/* Exercise %e.  */
-void test_e_va (va_list va)
+/* Return a value in the range [MIN, MAX].  */
+int range (int min, int max)
 {
-  T ("%e");         /* { dg-warning "between 12 and 14 bytes" } */
-  T ("%+e");        /* { dg-warning "between 13 and 14 bytes" } */
-  T ("% e");        /* { dg-warning "between 13 and 14 bytes" } */
-  T ("%#e");        /* { dg-warning "between 12 and 14 bytes" } */
-  T ("%#+e");       /* { dg-warning "between 13 and 14 bytes" } */
-  T ("%# e");       /* { dg-warning "between 13 and 14 bytes" } */
-
-  T ("%.e");        /* { dg-warning "between 5 and 7 bytes" } */
-  T ("%.0e");       /* { dg-warning "between 5 and 7 bytes" } */
-  T ("%.1e");       /* { dg-warning "between 7 and 9 bytes" } */
-  T ("%.2e");       /* { dg-warning "between 8 and 10 bytes" } */
-  T ("%.99e");      /* { dg-warning "between 105 and 107 bytes" } */
-  T ("%.199e");     /* { dg-warning "between 205 and 207 bytes" } */
-  T ("%.1099e");    /* { dg-warning "between 1105 and 1107 bytes" } */
-
-  T ("%0.e");       /* { dg-warning "between 5 and 7 bytes" } */
-  T ("%1.e");       /* { dg-warning "between 5 and 7 bytes" } */
-  T ("%1.e");       /* { dg-warning "between 5 and 7 bytes" } */
-  T ("%3.e");       /* { dg-warning "between 5 and 7 bytes" } */
-  T ("%6.e");       /* { dg-warning "between 6 and 7 bytes" } */
-  T ("%7.e");       /* { dg-warning "writing 7 bytes" } */
-
-  T ("%.*e");       /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-  T ("%1.*e");      /* { dg-warning "writing between 5 and 2147483655 bytes" } */
-  T ("%6.*e");      /* { dg-warning "writing between 6 and 2147483655 bytes" } */
-  T ("%9.*e");      /* { dg-warning "writing between 9 and 2147483655 bytes" } */
-
-  T ("%*.*e");      /* { dg-warning "writing between 5 and 2147483655 bytes" } */
+  int val = value ();
+  return val < min || max < val ? min : val;
 }
 
-/* Exercise %f.  */
-void test_f_va (va_list va)
+#define R(min, max) range (min, max)
+
+/* Verify that the checker can detect buffer overflow when the "%s"
+   argument is in a known range of lengths and one or both of which
+   exceed the size of the destination.  */
+
+void test_sprintf_chk_string (const char *s)
 {
-  T ("%f");         /* { dg-warning "between 8 and 317 bytes" } */
-  T ("%+f");        /* { dg-warning "between 9 and 317 bytes" } */
-  T ("% f");        /* { dg-warning "between 9 and 317 bytes" } */
-  T ("%#f");        /* { dg-warning "between 8 and 317 bytes" } */
-  T ("%+f");        /* { dg-warning "between 9 and 317 bytes" } */
-  T ("% f");        /* { dg-warning "between 9 and 317 bytes" } */
-  T ("%#+f");       /* { dg-warning "between 9 and 317 bytes" } */
-  T ("%# f");       /* { dg-warning "between 9 and 317 bytes" } */
-
-  T ("%.f");        /* { dg-warning "between 1 and 310 bytes" } */
-  T ("%.0f");       /* { dg-warning "between 1 and 310 bytes" } */
-  T ("%.1f");       /* { dg-warning "between 3 and 312 bytes" } */
-  T ("%.2f");       /* { dg-warning "between 4 and 313 bytes" } */
-  T ("%.99f");      /* { dg-warning "between 101 and 410 bytes" } */
-  T ("%.199f");     /* { dg-warning "between 201 and 510 bytes" } */
-  T ("%.1099f");    /* { dg-warning "between 1101 and 1410 bytes" } */
-
-  T ("%0.0f");      /* { dg-warning "between 1 and 310 bytes" } */
-  T ("%1.0f");      /* { dg-warning "between 1 and 310 bytes" } */
-  T ("%2.0f");      /* { dg-warning "between 2 and 310 bytes" } */
-  T ("%3.0f");      /* { dg-warning "between 3 and 310 bytes" } */
-  T ("%310.0f");    /* { dg-warning "writing 310 bytes" } */
-  T ("%311.0f");    /* { dg-warning "writing 311 bytes" } */
-  T ("%312.312f");  /* { dg-warning "between 314 and 623 bytes" } */
-  T ("%312.313f");  /* { dg-warning "between 315 and 624 bytes" } */
-
-  T ("%.*f");       /* { dg-warning "writing between 1 and 2147483958 bytes" } */
-  T ("%1.*f");      /* { dg-warning "writing between 1 and 2147483958 bytes" } */
-  T ("%3.*f");      /* { dg-warning "writing between 3 and 2147483958 bytes" } */
-
-  T ("%*.*f");      /* { dg-warning "writing between 1 and 2147483958 bytes" } */
+  T (1, "%*s", R (0, 1), "");     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", R (-2, -1), "");   /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*s", R (-3,  2), "");   /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*s", R (-4,  5), "");   /* { dg-warning "writing up to 5 bytes" } */
+
+  T (1, "%*s", R ( -5, 6), "1");  /* { dg-warning "writing between 1 and 6 bytes" } */
+  T (1, "%*s", R ( -6, 7), "12"); /* { dg-warning "writing between 2 and 7 bytes" } */
+
+  T (1, "%.*s", R (0, 1), "");
+  T (1, "%.*s", R (0, 1), s);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R (-2, -1), "");
+  T (1, "%.*s", R (-2, -1), s);   /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R (-3,  2), "");
+  T (1, "%.*s", R (-4,  5), "");
+
+  T (1, "%.*s", R ( -5, 6), "1");  /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R ( -6, 7), "12"); /* { dg-warning "writing up to 2 bytes " } */
+  T (1, "%.*s", R (  1, 7), "12"); /* { dg-warning "writing between 1 and 2 bytes " } */
+  T (1, "%.*s", R (  2, 7), "12"); /* { dg-warning "writing 2 bytes " } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "");     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "");     /* { dg-warning "directive writing up to 2 bytes into a region of size 1" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "");     /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "1");    /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "1");    /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "1");    /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "12");   /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "12");   /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "12");   /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "123");  /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 2), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 2), R (0, 2), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 2), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 2), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 2), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 3), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (1, 1), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 2), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (0, 3), s);      /* { dg-warning "writing between 1 and 3 bytes" } */
+
+  T (1, "%*.*s", R (1, 1), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 2), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (1, 3), s);      /* { dg-warning "writing between 1 and 3 bytes" } */
+
+  T (1, "%*.*s", R (2, 3), R (1, 3), "123");  /* { dg-warning "writing between 2 and 3 bytes" } */
+  T (1, "%*.*s", R (3, 4), R (1, 3), "123");  /* { dg-warning "writing between 3 and 4 bytes" } */
+  T (1, "%*.*s", R (4, 5), R (1, 3), "123");  /* { dg-warning "writing between 4 and 5 bytes" } */
+  T (1, "%*.*s", R (2, 3), R (1, 3), s);      /* { dg-warning "writing between 2 and 3 bytes" } */
 }
 
-/* Exercise %g.  The expected output is the lesser of %e and %f.  */
-void test_g_va (va_list va)
+void test_sprintf_chk_int (int w, int p, int i)
 {
-  T ("%g");         /* { dg-warning "between 1 and 13 bytes" } */
-  T ("%+g");        /* { dg-warning "between 2 and 13 bytes" } */
-  T ("% g");        /* { dg-warning "between 2 and 13 bytes" } */
-  T ("%#g");        /* { dg-warning "between 1 and 13 bytes" } */
-  T ("%#+g");       /* { dg-warning "between 2 and 13 bytes" } */
-  T ("%# g");       /* { dg-warning "between 2 and 13 bytes" } */
-
-  T ("%.g");        /* { dg-warning "between 1 and 7 bytes" } */
-  T ("%.0g");       /* { dg-warning "between 1 and 7 bytes" } */
-  T ("%.1g");       /* { dg-warning "between 1 and 7 bytes" } */
-  T ("%.2g");       /* { dg-warning "between 1 and 9 bytes" } */
-  T ("%.99g");      /* { dg-warning "between 1 and 106 bytes" } */
-  T ("%.199g");     /* { dg-warning "between 1 and 206 bytes" } */
-  T ("%.1099g");    /* { dg-warning "between 1 and 310 bytes" } */
-
-  T ("%0.0g");      /* { dg-warning "between 1 and 7 bytes" } */
-  T ("%1.0g");      /* { dg-warning "between 1 and 7 bytes" } */
-  T ("%2.0g");      /* { dg-warning "between 2 and 7 bytes" } */
-  T ("%3.0g");      /* { dg-warning "between 3 and 7 bytes" } */
-  T ("%7.0g");      /* { dg-warning "writing 7 bytes" } */
-  T ("%310.0g");    /* { dg-warning "writing 310 bytes" } */
-  T ("%311.0g");    /* { dg-warning "writing 311 bytes" } */
-  T ("%312.312g");  /* { dg-warning "writing 312 bytes" } */
-  T ("%312.313g");  /* { dg-warning "writing 312 bytes" } */
-  T ("%333.999g");  /* { dg-warning "writing 333 bytes" } */
-
-  T ("%.*g");       /* { dg-warning "writing between 1 and 310 bytes" } */
-  T ("%1.*g");      /* { dg-warning "writing between 1 and 310 bytes" } */
-  T ("%4.*g");      /* { dg-warning "writing between 4 and 310 bytes" } */
-
-  T ("%*.*g");      /* { dg-warning "writing between 1 and 2147483648 bytes" } */
+  T (1, "%*d", w, 0);             /* { dg-warning "may write a terminating nul|directive writing between 1 and \[0-9\]+ bytes" } */
+  T (1, "%*d", w, i);             /* { dg-warning "may write a terminating nul|directive writing between 1 and \[0-9\]+ bytes" } */
+
+  T (1, "%*d", R (-1, 1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%*d", R ( 0, 1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%+*d", R ( 0, 1), 0);    /* { dg-warning "directive writing 2 bytes" } */
+  T (1, "%+*u", R ( 0, 1), 0);    /* { dg-warning "writing a terminating nul" } */
+  T (2, "%*d", R (-3, -2), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-3, -1), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-3,  0), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-2, -1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R (-2,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R (-1,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R ( 0,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R ( 1,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+
+  T (1, "%.*d", p, 0);             /* { dg-warning "may write a terminating nul|directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", p, i);             /* { dg-warning "may write a terminating nul||directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", R (INT_MIN, -1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.*d", R (INT_MIN,  0), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R (-2, -1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.*d", R (-1,  1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R ( 0,  1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R ( 0,  2), 0);     /* { dg-warning "directive writing up to 2 bytes" } */
+  T (1, "%.*d", R ( 0,  INT_MAX - 1), 0);     /* { dg-warning "directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", R ( 1,  INT_MAX - 1), 0);     /* { dg-warning "directive writing between 1 and \[0-9\]+ bytes" } */
 }
+
+/* { dg-prune-output "flag used with .%.. gnu_printf format" } */