diff mbox

correct handling of negative-positive precision ranges with floating directives (PR 79800)

Message ID 10b80f30-8fd3-bc2c-0a09-64583dac88a7@gmail.com
State New
Headers show

Commit Message

Martin Sebor March 14, 2017, 12:33 a.m. UTC
The output of a floating point directive whose precision is
specified by an asterisk with an argument that's in a range
that includes both negative and positive values less than
six may include between zero and six fractional digits plus
a decimal point.  For example,

   printf ("%.*e", p, x);

results in the 14 bytes

   -1.797693e+308

when p == -1 and x == -DBL_MIN because a negative precision
is ignored and the directive assumes the default 6, and in
just the one byte

   0

when p == 0 and x == 0.0.

Current trunk doesn't handle this case correctly when the
floating argument isn't known and uses the upper bound of
the specified precision as the maximum number of fractional
digits.  As a result, it sets the range on the return value
in this case as [5, 7] (plus 5 for the longest multibyte
decimal decimal point) when the correct range is [5, 14] as
explained above (plus 5 again).

The attached patch corrects the handling of such precision
ranges to avoid this unlikely bug.

Martin

Comments

Jeff Law March 15, 2017, 4:31 a.m. UTC | #1
On 03/13/2017 06:33 PM, Martin Sebor wrote:
> The output of a floating point directive whose precision is
> specified by an asterisk with an argument that's in a range
> that includes both negative and positive values less than
> six may include between zero and six fractional digits plus
> a decimal point.  For example,
>
>   printf ("%.*e", p, x);
>
> results in the 14 bytes
>
>   -1.797693e+308
>
> when p == -1 and x == -DBL_MIN because a negative precision
> is ignored and the directive assumes the default 6, and in
> just the one byte
>
>   0
>
> when p == 0 and x == 0.0.
>
> Current trunk doesn't handle this case correctly when the
> floating argument isn't known and uses the upper bound of
> the specified precision as the maximum number of fractional
> digits.  As a result, it sets the range on the return value
> in this case as [5, 7] (plus 5 for the longest multibyte
> decimal decimal point) when the correct range is [5, 14] as
> explained above (plus 5 again).
>
> The attached patch corrects the handling of such precision
> ranges to avoid this unlikely bug.
>
> Martin
>
> gcc-79800.diff
>
>
> PR tree-optimization/79800 - wrong snprintf result range with precision in a narrow negative-positive range
>
> gcc/ChangeLog:
>
> 	PR tree-optimization/79800
> 	* gimple-ssa-sprintf.c (format_floating: Add argument.  Handle
> 	precision in negative-positive range.
> 	(format_floating): Call non-const overload with adjusted precision.
>
> gcc/testsuite/ChangeLog:
>
> 	PR tree-optimization/79800
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-15.c: Add test cases.
> 	* gcc.dg/tree-ssa/pr79800.c: New test.
Thanks.  Installed.

jeff
diff mbox

Patch

PR tree-optimization/79800 - wrong snprintf result range with precision in a narrow negative-positive range

gcc/ChangeLog:

	PR tree-optimization/79800
	* gimple-ssa-sprintf.c (format_floating: Add argument.  Handle
	precision in negative-positive range.
	(format_floating): Call non-const overload with adjusted precision.

gcc/testsuite/ChangeLog:

	PR tree-optimization/79800
	* gcc.dg/tree-ssa/builtin-sprintf-warn-15.c: Add test cases.
	* gcc.dg/tree-ssa/pr79800.c: New test.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 0448b21..2474391 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -1499,11 +1499,13 @@  format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
 }
 
 /* Return a range representing the minimum and maximum number of bytes
-   that the directive DIR will output for any argument.  This function
-   is used when the directive argument or its value isn't known.  */
+   that the directive DIR will output for any argument.  PREC gives
+   the adjusted precision range to account for negative precisions
+   meaning the default 6.  This function is used when the directive
+   argument or its value isn't known.  */
 
 static fmtresult
-format_floating (const directive &dir)
+format_floating (const directive &dir, const HOST_WIDE_INT prec[2])
 {
   tree type;
 
@@ -1532,8 +1534,8 @@  format_floating (const directive &dir)
   /* The minimum output as determined by flags.  It's always at least 1.
      When plus or space are set the output is preceded by either a sign
      or a space.  */
-  int flagmin = (1 /* for the first digit */
-		 + (dir.get_flag ('+') | dir.get_flag (' ')));
+  unsigned flagmin = (1 /* for the first digit */
+		      + (dir.get_flag ('+') | dir.get_flag (' ')));
 
   /* When the pound flag is set the decimal point is included in output
      regardless of precision.  Whether or not a decimal point is included
@@ -1557,14 +1559,13 @@  format_floating (const directive &dir)
 			 + minprec
 			 + 3 /* p+0 */);
 
-	res.range.max = format_floating_max (type, 'a', dir.prec[1]);
+	res.range.max = format_floating_max (type, 'a', prec[1]);
 	res.range.likely = res.range.min;
 
 	/* The unlikely maximum accounts for the longest multibyte
 	   decimal point character.  */
 	res.range.unlikely = res.range.max;
-	if (dir.prec[0] != dir.prec[1]
-	    || dir.prec[0] == -1 || dir.prec[0] > 0)
+	if (dir.prec[1] > 0)
 	  res.range.unlikely += target_mb_len_max () - 1;
 
 	break;
@@ -1573,23 +1574,18 @@  format_floating (const directive &dir)
     case 'E':
     case 'e':
       {
+	/* Minimum output attributable to precision and, when it's
+	   non-zero, decimal point.  */
+	HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
+
 	/* 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[0] < 0 && dir.prec[1] > -1) || dir.prec[0] == 0)
-	  minprec = 0;
-	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.  */
-	int maxprec = dir.prec[1] < 0 ? 6 : dir.prec[1];
-	res.range.max = format_floating_max (type, 'e', maxprec);
 
+	res.range.max = format_floating_max (type, 'e', prec[1]);
 	res.range.likely = res.range.min;
 
 	/* The unlikely maximum accounts for the longest multibyte
@@ -1605,21 +1601,19 @@  format_floating (const directive &dir)
     case 'F':
     case 'f':
       {
+	/* Minimum output attributable to precision and, when it's non-zero,
+	   decimal point.  */
+	HOST_WIDE_INT minprec = prec[0] ? prec[0] + !radix : 0;
+
 	/* The lower bound when precision isn't specified is 8 bytes
 	   ("1.23456" since precision is taken to be 6).  When precision
 	   is zero, the lower bound is 1 byte (e.g., "1").  Otherwise,
 	   when precision is greater than zero, then the lower bound
 	   is 2 plus precision (plus flags).  */
-	HOST_WIDE_INT minprec = 0;
-	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.  */
-	res.range.max = format_floating_max (type, 'f', dir.prec[1]);
+	res.range.max = format_floating_max (type, 'f', prec[1]);
 
 	/* The minimum output with unknown precision is a single byte
 	   (e.g., "0") but the more likely output is 3 bytes ("0.0").  */
@@ -1659,6 +1653,8 @@  format_floating (const directive &dir)
 	    else if (maxprec < 0)
 	      maxprec = 5;
 	  }
+	else
+	  maxprec = prec[1];
 
 	res.range.max = format_floating_max (type, spec, maxprec);
 
@@ -1702,9 +1698,6 @@  format_floating (const directive &dir)
 static fmtresult
 format_floating (const directive &dir, tree arg)
 {
-  if (!arg || TREE_CODE (arg) != REAL_CST)
-    return format_floating (dir);
-
   HOST_WIDE_INT prec[] = { dir.prec[0], dir.prec[1] };
 
   /* For an indeterminate precision the lower bound must be assumed
@@ -1767,6 +1760,9 @@  format_floating (const directive &dir, tree arg)
 	}
     }
 
+  if (!arg || TREE_CODE (arg) != REAL_CST)
+    return format_floating (dir, prec);
+
   /* The minimum and maximum number of bytes produced by the directive.  */
   fmtresult res;
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-15.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-15.c
index c38a656..0b863a8 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-15.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-15.c
@@ -113,9 +113,16 @@  void test_unknown_precision_integer (int p, int i, double d)
 
 void test_unknown_precision_floating (int p, double d)
 {
+  T ( 0, "%.*a", R (-1, 0), d); /* { dg-warning "between 6 and 24 " } */
+  T ( 6, "%.*a", R (-1, 0), d); /* { dg-warning "writing a terminating nul" } */
+  T ( 7, "%.*a", R (-1, 0), d);
   T ( 7, "%.*a", p, d);
   T (21, "%.*a", p, 3.141);
 
+  T ( 0, "%.*e",  R (-1, 0), d); /* { dg-warning "between 5 and 14 " } */
+  T ( 0, "%.*e",  R (-1, 6), d); /* { dg-warning "between 5 and 14 " } */
+  T ( 5, "%.*e",  R (-1, 6), d); /* { dg-warning "writing a terminating nul" } */
+  T ( 6, "%.*e",  R (-1, 6), d);
   /* "%.0e", 0.0 results in 5 bytes: "0e+00"  */
   T ( 5, "%.*e",  p, d);      /* { dg-warning "writing a terminating nul" } */
   /* "%#.0e", 0.0 results in 6 bytes: "0.e+00"  */
@@ -125,6 +132,10 @@  void test_unknown_precision_floating (int p, double d)
   T ( 6, "%#.*e", p, 3.141);  /* { dg-warning "writing a terminating nul" } */
   T ( 7, "%#.*e", p, 3.141);
 
+  T ( 0, "%.*f",  R (-1, 0), d); /* { dg-warning "between 1 and 317 " } */
+  T ( 0, "%.*f",  R (-1, 6), d); /* { dg-warning "between 1 and 317 " } */
+  T ( 3, "%.*f",  R (-1, 6), d); /* { dg-warning "may write a terminating nul" } */
+  T ( 4, "%.*f",  R (-1, 6), d);
   /* "%.0f", 0.0 results in 1 byte: "0" but precision of at least 1
      is likely, resulting in "0.0".  */
   T ( 3, "%.*f",  p, d);   /* { dg-warning "may write a terminating nul" } */
@@ -138,12 +149,16 @@  void test_unknown_precision_floating (int p, double d)
   T ( 3, "%#.*f", p, 3.141); /* { dg-warning "may write a terminating nul" } */
   T ( 4, "%#.*f", p, 3.141);
 
+  T ( 0, "%.*g",  R (-1, 0), d); /* { dg-warning "between 1 and 13 " } */
+  T (12, "%.*g",  R (-1, 0), d); /* { dg-warning "may write a terminating nul" } */
+  T (13, "%.*g",  R (-1, 0), d);
   T (12, "%.*g",  p, d);   /* { dg-warning "may write a terminating nul" } */
   T (12, "%#.*g", p, d);   /* { dg-warning "may write a terminating nul" } */
   T (13, "%.*g",  p, d);
   T (13, "%#.*g", p, d);
-  T ( 6, "%#.*g", R (-1, 0), d);/* { dg-warning "may write a terminating nul" } */
-  T ( 7, "%#.*g", R (-1, 0), d);
+  T (12, "%#.*g", R (-1, 0), d);/* { dg-warning "may write a terminating nul" } */
+  T (12, "%#.*g", R (-1, 6), d);/* { dg-warning "may write a terminating nul" } */
+  T (13, "%#.*g", R (-1, 0), d);
   T ( 6, "%#.*g", R ( 0, 0), d);/* { dg-warning "may write a terminating nul" } */
   T ( 7, "%#.*g", R ( 0, 0), d);
   T ( 6, "%#.*g", R ( 0, 1), d);/* { dg-warning "may write a terminating nul" } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr79800.c b/gcc/testsuite/gcc.dg/tree-ssa/pr79800.c
new file mode 100644
index 0000000..180a6e7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr79800.c
@@ -0,0 +1,31 @@ 
+/* PR 79800 - wrong snprintf result range with precision in a narrow
+   negative-positive range
+   { dg-do "run" }
+   { dg-options "-O2 -Wall" } */
+
+#define FMT "%.*a"
+char fmt[] = FMT;
+
+volatile double x = 1.23456789;
+
+void f (int p)
+{
+  if (p < -1 || 0 < p)
+    p = -1;
+
+  char d[30];
+  int n1 = __builtin_sprintf (d, "%.*a", p, x);
+  const char *s = n1 < 20 ? "< 20" : ">= 20";
+
+  if (__builtin_strcmp (s, ">= 20"))
+    __builtin_abort ();
+}
+
+volatile int i = -1;
+
+int main ()
+{
+  f (i);
+
+  return 0;
+}