diff mbox

avoid using upper bound of width and precision in -Wformat-overlow (PR 79692)

Message ID 6ced17e1-aa9d-d7cd-92f7-debabe7c6050@gmail.com
State New
Headers show

Commit Message

Martin Sebor Feb. 25, 2017, 10:46 p.m. UTC
In an arithmetic directive with the width or precision specified
by an argument to the asterisk (e.g., "%*x") and where the argument
range is unknown, for the purposes of the return value optimization
the pass must assume it's potentially as large as INT_MAX.  But for
the purposes of issuing a warning, that assumption leads to false
positives because the value of the argument can and in reality
usually is much smaller.

The attached patch adjusts the checker to use the lower bound in
this case to avoid these false positives.  It does that for both
integer and floating directives (for the latter it uses the lesser
of 3 and the lower bound in this case).

In addition, the patch corrects the handling of the pound flag ('#')
in "%#.*g" directives which makes it keep as many trailing zeros after
the radix character as specified by the precision.  (Without the '#',
"%.*g" trims trailing zeros.)

Martin

Comments

Jeff Law Feb. 27, 2017, 4:21 p.m. UTC | #1
On 02/25/2017 03:46 PM, Martin Sebor wrote:
> In an arithmetic directive with the width or precision specified
> by an argument to the asterisk (e.g., "%*x") and where the argument
> range is unknown, for the purposes of the return value optimization
> the pass must assume it's potentially as large as INT_MAX.  But for
> the purposes of issuing a warning, that assumption leads to false
> positives because the value of the argument can and in reality
> usually is much smaller.
>
> The attached patch adjusts the checker to use the lower bound in
> this case to avoid these false positives.  It does that for both
> integer and floating directives (for the latter it uses the lesser
> of 3 and the lower bound in this case).
>
> In addition, the patch corrects the handling of the pound flag ('#')
> in "%#.*g" directives which makes it keep as many trailing zeros after
> the radix character as specified by the precision.  (Without the '#',
> "%.*g" trims trailing zeros.)
>
> Martin
>
>
>
> gcc-79692.diff
>
>
> PR middle-end/79692 - [7 Regression] -Wformat-overflow false positive
>
> gcc/ChangeLog:
>
> 	PR c/79692
> 	* gimple-ssa-sprintf.c
> 	(directive::known_width_and_precision): New function.
> 	(format_integer): Use it.
> 	(get_mpfr_format_length): Consider the full range of precision
> 	when computing %g output with the # flag.  Set the likely byte
> 	count to 3 rather than 1 when precision is indeterminate.
> 	(format_floating): Correct the lower bound of precision.
>
> gcc/testsuite/ChangeLog:
>
> 	PR c/79692
> 	* gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-10.c: Correct %#g.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-15.c: New test.

So in some cases you use

+  /* For an indeterminate precision the lower bound must be assumed
+     to be zero.  */
+  if (prec[0] < 0 && prec[1] >= 0)

Note prec[1] >= 0

In other cases you have:

+	/* The minimum output with unknown precision is a single byte
+	   (e.g., "0") but the more likely output is 3 bytes ("0.0").  */
+	if (dir.prec[0] < 0 && dir.prec[1] > 0)

Note  dir.prec[1] > 0

Shouldn't one of those be changed to be consistent with the other?

Similarly in known_width_and_precision.  Please review the patch to 
ensure that we're as consistent as possible for these tests.


OK after doing that review and making any necessary trivial adjustments.

Jeff
Martin Sebor March 1, 2017, 11:48 p.m. UTC | #2
> So in some cases you use
>
> +  /* For an indeterminate precision the lower bound must be assumed
> +     to be zero.  */
> +  if (prec[0] < 0 && prec[1] >= 0)
>
> Note prec[1] >= 0
>
> In other cases you have:
>
> +    /* The minimum output with unknown precision is a single byte
> +       (e.g., "0") but the more likely output is 3 bytes ("0.0").  */
> +    if (dir.prec[0] < 0 && dir.prec[1] > 0)
>
> Note  dir.prec[1] > 0
>
> Shouldn't one of those be changed to be consistent with the other?

Thanks for the careful review!  The two tests determine two different
things so I'm not sure they need to be consistent. But considering
your question made me realize that the first conditional isn't
completely correct:

+  /* For an indeterminate precision the lower bound must be assumed
+     to be zero.  */
+  if (prec[0] < 0 && prec[1] >= 0)
+    prec[0] = 0;

Precisions in a negative-positive range with an upper bound of less
than 6 must be assumed to have an upper bound of 6 because that's
the default when precision is negative.  E.g., given
snprintf (0, 0, "%*f", p, 1.23456789) where p is in [-1, 0] results
either in:

   1.234568

when (p == -1) holds, or in:

   1

when (p == 0) holds.  So while the lower bound in the if statement
above must be set to zero, the upper bound may need to be adjusted
as well.

The patch I just committed in r245822 fixes that (and also changes
the conditional so that consistency is no longer an issue).

However, while reviewing the rest of the floating point handling
code it became clear that this is a bug that affects both floating
point formatting functions (i.e., the one that handles constants
as well as the non-constant one).  I also noticed some other minor
glitches in this area that should be fixed.  I'm testing another
patch that resolves those problem as well.

> Similarly in known_width_and_precision.  Please review the patch to
> ensure that we're as consistent as possible for these tests.

In known_width_and_precision the different inequalities are
deliberate.  Because the actual width is an absolute value
of the specified argument the range of unknown width is
[0, INT_MAX + 1] (printf("%*i", INT_MIN, i) specifies a width
of -INT_MIN, or INT_MAX + 1 (without overflow).  But because
negative precisions are ignored, the largest upper bound is
INT_MAX.

Martin
diff mbox

Patch

PR middle-end/79692 - [7 Regression] -Wformat-overflow false positive

gcc/ChangeLog:

	PR c/79692
	* gimple-ssa-sprintf.c
	(directive::known_width_and_precision): New function.
	(format_integer): Use it.
	(get_mpfr_format_length): Consider the full range of precision
	when computing %g output with the # flag.  Set the likely byte
	count to 3 rather than 1 when precision is indeterminate.
	(format_floating): Correct the lower bound of precision.

gcc/testsuite/ChangeLog:

	PR c/79692
	* gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-10.c: Correct %#g.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-15.c: New test.

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 7688439..6777092 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -692,6 +692,16 @@  struct directive
   {
     get_int_range (arg, integer_type_node, prec, prec + 1, false, -1);
   }
+
+  /* Return true if both width and precision are known to be
+     either constant or in some range, false otherwise.  */
+  bool known_width_and_precision () const
+  {
+    return ((width[1] < 0
+	     || (unsigned HOST_WIDE_INT)width[1] <= target_int_max ())
+	    && (prec[1] < 0
+		|| (unsigned HOST_WIDE_INT)prec[1] < target_int_max ()));
+  }
 };
 
 /* Return the logarithm of X in BASE.  */
@@ -1180,10 +1190,10 @@  format_integer (const directive &dir, tree 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
 	     specified, and for signed conversions in base 8 and 10 when
-	     flags when either the space or '+' flag has been specified
-	     when it results in just one byte (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.  */
+	     either the space or '+' flag has been specified and it results
+	     in just one byte (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.  */
 	  res.range.min = ((base == 8 && dir.get_flag ('#')) || maybesign);
 	  if (res.range.min == 0 && dir.prec[0] != dir.prec[1])
 	    {
@@ -1254,10 +1264,12 @@  format_integer (const directive &dir, tree arg)
 	  argmax = wide_int_to_tree (argtype, max);
 
 	  /* Set KNOWNRANGE if the argument is in a known subrange
-	     of the directive's type (KNOWNRANGE may be reset below).  */
+	     of the directive's type and neither width nor precision
+	     is unknown.  (KNOWNRANGE may be reset below).  */
 	  res.knownrange
-	    = (!tree_int_cst_equal (TYPE_MIN_VALUE (dirtype), argmin)
-	       || !tree_int_cst_equal (TYPE_MAX_VALUE (dirtype), argmax));
+	    = ((!tree_int_cst_equal (TYPE_MIN_VALUE (dirtype), argmin)
+		|| !tree_int_cst_equal (TYPE_MAX_VALUE (dirtype), argmax))
+	       && dir.known_width_and_precision ());
 
 	  res.argmin = argmin;
 	  res.argmax = argmax;
@@ -1421,12 +1433,12 @@  get_mpfr_format_length (mpfr_ptr x, const char *flags, HOST_WIDE_INT prec,
 
   HOST_WIDE_INT p = prec;
 
-  if (spec == 'G')
+  if (spec == 'G' && !strchr (flags, '#'))
     {
-      /* For G/g, precision gives the maximum number of significant
-	 digits which is bounded by LDBL_MAX_10_EXP, or, for a 128
-	 bit IEEE extended precision, 4932.  Using twice as much
-	 here should be more than sufficient for any real format.  */
+      /* For G/g without the pound flag, precision gives the maximum number
+	 of significant digits which is bounded by LDBL_MAX_10_EXP, or, for
+	 a 128 bit IEEE extended precision, 4932.  Using twice as much here
+	 should be more than sufficient for any real format.  */
       if ((IEEE_MAX_10_EXP * 2) < prec)
 	prec = IEEE_MAX_10_EXP * 2;
       p = prec;
@@ -1609,7 +1621,12 @@  format_floating (const directive &dir)
 	/* Compute the upper bound for -TYPE_MAX.  */
 	res.range.max = format_floating_max (type, 'f', dir.prec[1]);
 
-	res.range.likely = res.range.min;
+	/* The minimum output with unknown precision is a single byte
+	   (e.g., "0") but the more likely output is 3 bytes ("0.0").  */
+	if (dir.prec[0] < 0 && dir.prec[1] > 0)
+	  res.range.likely = 3;
+	else
+	  res.range.likely = res.range.min;
 
 	/* The unlikely maximum accounts for the longest multibyte
 	   decimal point character.  */
@@ -1625,10 +1642,43 @@  format_floating (const directive &dir)
 	/* The %g output depends on precision and the exponent of
 	   the argument.  Since the value of the argument isn't known
 	   the lower bound on the range of bytes (not counting flags
-	   or width) is 1.  */
-	res.range.min = flagmin;
-	res.range.max = format_floating_max (type, 'g', dir.prec[1]);
-	res.range.likely = res.range.max;
+	   or width) is 1 plus radix (i.e., either "0" or "0." for
+	   "%g" and "%#g", respectively, with a zero argument).  */
+	res.range.min = flagmin + radix;
+
+	char spec = 'g';
+	HOST_WIDE_INT maxprec = dir.prec[1];
+	if (radix && maxprec)
+	  {
+	    /* When the pound flag (radix) is set, trailing zeros aren't
+	       trimmed and so the longest output is the same as for %e,
+	       except with precision minus 1 (as specified in C11).  */
+	    spec = 'e';
+	    if (maxprec > 0)
+	      --maxprec;
+	    else if (maxprec < 0)
+	      maxprec = 5;
+	  }
+
+	res.range.max = format_floating_max (type, spec, maxprec);
+
+	/* The likely output is either the maximum computed above
+	   minus 1 (assuming the maximum is positive) when precision
+	   is known (or unspecified), or the same minimum as for %e
+	   (which is computed for a non-negative argument).  Unlike
+	   for the other specifiers above the likely output isn't
+	   the minimum because for %g that's 1 which is unlikely.  */
+	if (dir.prec[1] < 0
+	    || (unsigned HOST_WIDE_INT)dir.prec[1] < target_int_max ())
+	  res.range.likely = res.range.max - 1;
+	else
+	  {
+	    HOST_WIDE_INT minprec = 6 + !radix /* decimal point */;
+	    res.range.likely = (flagmin
+				+ radix
+				+ minprec
+				+ 2 /* e+ */ + 2);
+	  }
 
 	/* The unlikely maximum accounts for the longest multibyte
 	   decimal point character.  */
@@ -1657,6 +1707,11 @@  format_floating (const directive &dir, tree arg)
 
   HOST_WIDE_INT prec[] = { dir.prec[0], dir.prec[1] };
 
+  /* For an indeterminate precision the lower bound must be assumed
+     to be zero.  */
+  if (prec[0] < 0 && prec[1] >= 0)
+    prec[0] = 0;
+
   if (TOUPPER (dir.specifier) == 'A')
     {
       /* For %a, leave the minimum precision unspecified to let
@@ -1734,12 +1789,23 @@  format_floating (const directive &dir, tree arg)
       res.range.max = tmp;
     }
 
-  res.knownrange = true;
+  /* The range is known unless either width or precision is unknown.  */
+  res.knownrange = dir.known_width_and_precision ();
+
+  /* For the same floating point constant, unless width or precision
+     is unknown, use the longer output as the likely maximum since
+     with round to nearest either is equally likely.  Otheriwse, when
+     precision is unknown, use the greater of the minimum and 3 as
+     the likely output (for "0.0" since zero precision is unlikely).  */
+  if (res.knownrange)
+    res.range.likely = res.range.max;
+  else if (res.range.min < 3
+	   && dir.prec[0] < 0
+	   && (unsigned HOST_WIDE_INT)dir.prec[1] == target_int_max ())
+    res.range.likely = 3;
+  else
+    res.range.likely = res.range.min;
 
-  /* For the same floating point constant use the longer output
-     as the likely maximum since with round to nearest either is
-     equally likely.  */
-  res.range.likely = res.range.max;
   res.range.unlikely = res.range.max;
 
   if (res.range.max > 2 && (prec[0] != 0 || prec[1] != 0))
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
index b873a0c..8a13f33 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
@@ -7,7 +7,7 @@ 
    The test is compiled with warnings disabled to make sure the absence
    of optimizations does not depend on the presence of warnings.  */
 /* { dg-do compile } */
-/* { dg-options "-O2 -fprintf-return-value -fdump-tree-optimized -ftrack-macro-expansion=0 -w" } */
+/* { dg-options "-O2 -fprintf-return-value -fdump-tree-optimized -w" } */
 
 #ifndef LINE
 # define LINE 0
@@ -243,6 +243,14 @@  RNG (6,  6,  7, "%La",      0.0L)   /* Glibc output: "0x0p+0"  */
 RNG (6,  6,  7, "%La",      ld)
 RNG (6,  6,  7, "%.4096La", ld)
 
+/* Verify that the pound flag with unknown precision prevents the %g
+   directive from trimming trailing zeros as it otherwise does.  As
+   a consequence, the result must be assumed to be as large as
+   precision.  */
+RNG (1,  315,  316, "%#.*g", i, d);
+RNG (1, 4095, 4096, "%#.*g", i, d);
+RNG (1, 4095, 4096, "%#.*g", i, 0.0);
+
 /* Verify that the result of formatting an unknown string isn't optimized
    into a non-negative range.  The string could be longer that 4,095 bytes,
    resulting in the formatting function having undefined behavior (and
@@ -282,7 +290,7 @@  RNG (0,  6,   8, "%s%ls", "1", L"2");
 
 /*  Only conditional calls to must_not_eliminate must be made (with
     any probability):
-    { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 124 "optimized" { target { ilp32 || lp64 } } } }
-    { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 93 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } }
+    { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 127 "optimized" { target { ilp32 || lp64 } } } }
+    { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 96 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } }
     No unconditional calls to abort should be made:
     { dg-final { scan-tree-dump-not ";\n *must_not_eliminate" "optimized" } } */
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
index 5523acd..1213e89 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-10.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-10.c
@@ -239,9 +239,11 @@  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" } */
+
+  /* The pound flag means the radix character is always present.  */
+  T ("%#g");        /* { dg-warning "between 2 and 13 bytes" } */
+  T ("%#+g");       /* { dg-warning "between 3 and 13 bytes" } */
+  T ("%# g");       /* { dg-warning "between 3 and 13 bytes" } */
 
   T ("%.g");        /* { dg-warning "between 1 and 7 bytes" } */
   T ("%.0g");       /* { dg-warning "between 1 and 7 bytes" } */
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
new file mode 100644
index 0000000..c38a656
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-15.c
@@ -0,0 +1,197 @@ 
+/* PR middle-end/79692 - -Wformat-overflow false positive on an integer
+   directive with unknown width
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wformat-overflow=1 -ftrack-macro-expansion=0" }
+   { dg-require-effective-target int32plus } */
+
+typedef __SIZE_TYPE__  size_t;
+typedef __WCHAR_TYPE__ wchar_t;
+
+#define INT_MAX __INT_MAX__
+#define INT_MIN (-INT_MAX - 1)
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+void sink (char*, char*);
+
+int dummy_sprintf (char*, const char*, ...);
+
+char buffer [1024];
+extern char *ptr;
+
+int int_range (int min, int max)
+{
+  extern int int_value (void);
+  int n = int_value ();
+  return n < min || max < n ? min : n;
+}
+
+unsigned uint_range (unsigned min, unsigned max)
+{
+  extern unsigned uint_value (void);
+  unsigned n = uint_value ();
+  return n < min || max < n ? min : n;
+}
+
+/* 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)
+
+/* 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)
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#define T(size, ...)						\
+  (FUNC (sprintf) (buffer (size),  __VA_ARGS__),		\
+   sink (buffer, ptr))
+
+/* Return a signed integer in the range [MIN, MAX].  */
+#define R(min, max)  int_range (min, max)
+
+void test_unknown_width_integer (int w, int i)
+{
+  T (10, "%*d", w, i);
+  T (10, "%*d", w, R (0, 12345));
+
+  T (10, "%*i", w, i);
+  T (10, "%*i", w, R (0, 12345));
+
+  T (10, "%*o", w, i);
+  T (10, "%*o", w, R (0, 12345));
+
+  T (10, "%*i", w, i);
+  T (10, "%*i", w, R (0, 12345));
+}
+
+void test_unknown_width_floating (int w, double d)
+{
+  T ( 7, "%*a", w, d);
+  T (21, "%*a", w, 3.141);
+
+  T (12, "%*e",  w, d);    /* { dg-warning "writing a terminating nul" } */
+  T (12, "%#*e", w, d);    /* { dg-warning "writing a terminating nul" } */
+  T (13, "%*e",  w, d);
+  T (13, "%#*e", w, d);
+  T (13, "%*e",  w, 3.141);
+
+  T ( 8, "%*f",  w, d);   /* { dg-warning "writing a terminating nul" } */
+  T ( 8, "%#*f", w, d);   /* { dg-warning "writing a terminating nul" } */
+  T ( 9, "%*f",  w, d);
+  T ( 9, "%#*f", w, d);
+  T ( 9, "%*f",  w, 3.141);
+  T ( 9, "%#*f", w, 3.141);
+
+  T (12, "%*g", w, d);   /* { dg-warning "may write a terminating nul" } */
+  T (13, "%*g", w, d);
+  T (13, "%*g", w, 3.141);
+}
+
+void test_unknown_precision_integer (int p, int i, double d)
+{
+  T (10, "%.*d", p, i);
+  T (10, "%.*d", p, R (0, 12345));
+
+  T (10, "%.*i", p, i);
+  T (10, "%.*i", p, R (0, 12345));
+
+  T (10, "%.*o", p, i);
+  T (10, "%.*o", p, R (0, 12345));
+
+  T (10, "%.*i", p, i);
+  T (10, "%.*i", p, R (0, 12345));
+}
+
+void test_unknown_precision_floating (int p, double d)
+{
+  T ( 7, "%.*a", p, d);
+  T (21, "%.*a", p, 3.141);
+
+  /* "%.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"  */
+  T ( 6, "%#.*e", p, d);      /* { dg-warning "writing a terminating nul" } */
+  T ( 6, "%.*e",  p, d);
+  T ( 6, "%.*e",  p, 3.141);
+  T ( 6, "%#.*e", p, 3.141);  /* { dg-warning "writing a terminating nul" } */
+  T ( 7, "%#.*e", p, 3.141);
+
+  /* "%.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" } */
+  /* "%#.0f", 0.0 results in 2 bytes: "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" } */
+  T ( 4, "%.*f",  p, d);
+  T ( 4, "%#.*f", p, d);
+  T ( 3, "%.*f",  p, 3.141); /* { dg-warning "may write a terminating nul" } */
+  T ( 4, "%.*f",  p, 3.141);
+  T ( 3, "%#.*f", p, 3.141); /* { dg-warning "may write a terminating nul" } */
+  T ( 4, "%#.*f", p, 3.141);
+
+  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 ( 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" } */
+  T ( 7, "%#.*g", R ( 0, 1), d);
+  T ( 3, "%.*g",  p, 3.141); /* { dg-warning "may write a terminating nul" } */
+  T ( 4, "%.*g",  p, 3.141);
+  T ( 3, "%#.*g", p, 3.141); /* { dg-warning "may write a terminating nul" } */
+  T ( 4, "%#.*g", p, 3.141);
+}
+
+
+void test_unknown_width_and_precision_integer (int w, int p, int i)
+{
+  T (10, "%*.*d", w, p, i);
+  T (10, "%*.*d", w, p, R (0, 12345));
+
+  T (10, "%*.*i", w, p, i);
+  T (10, "%*.*i", w, p, R (0, 12345));
+
+  T (10, "%*.*o", w, p, i);
+  T (10, "%*.*o", w, p, R (0, 12345));
+
+  T (10, "%*.*i", w, p, i);
+  T (10, "%*.*i", w, p, R (0, 12345));
+}
+
+void test_unknown_width_and_precision_floating (int w, int p, double d)
+{
+  T ( 7, "%*.*a", w, p, d);
+  T (21, "%*.*a", w, p, 3.141);
+
+  /* "%0.0e", 0.0 results in 5 bytes: "0e+00"  */
+  T ( 5, "%*.*e",  w, p, d);   /* { dg-warning "writing a terminating nul" } */
+  /* "%#0.0e", 0.0 results in 6 bytes: "0.e+00"  */
+  T ( 6, "%#*.*e", w, p, d);   /* { dg-warning "writing a terminating nul" } */
+  T ( 6, "%*.*e",  w, p, d);
+  T ( 6, "%*.*e",  w, p, 3.141);
+  T ( 6, "%#*.*e", w, p, 3.141);/* { dg-warning "writing a terminating nul" } */
+  T ( 7, "%#*.*e", w, p, 3.141);
+
+  T ( 3, "%*.*f",  w, p, d);  /* { dg-warning "may write a terminating nul" } */
+  T ( 3, "%#*.*f", w, p, d);  /* { dg-warning "may write a terminating nul" } */
+  T ( 4, "%*.*f",  w, p, d);
+  T ( 4, "%*.*f",  w, p, 3.141);
+  T ( 4, "%#*.*f", w, p, 3.141);
+
+  T (13, "%*.*g",  w, p, d);
+  T (13, "%#*.*g", w, p, d);
+  T (13, "%*.*g",  w, p, 3.141);
+  T (13, "%#*.*g", w, p, 3.141);
+}