PR middle-end/79692 - [7 Regression] -Wformat-overflow false positive
authorMartin Sebor <msebor@redhat.com>
Wed, 1 Mar 2017 23:39:59 +0000 (23:39 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Wed, 1 Mar 2017 23:39:59 +0000 (16:39 -0700)
gcc/ChangeLog:

PR middle-end/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 middle-end/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.
* gcc.dg/tree-ssa/builtin-snprintf-3.c: Ditto.

From-SVN: r245822

gcc/ChangeLog
gcc/gimple-ssa-sprintf.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-10.c
gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-15.c [new file with mode: 0644]

index 3580685fb5fb2bb40c9a3f27e990e9b29e943711..4f44086608112d384396eff95eac1691c0461f58 100644 (file)
@@ -1,3 +1,14 @@
+2017-03-01  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/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.
+
 2017-03-01  Bill Schmidt  <wschmidt@linux.vnet.ibm.com>
 
        * doc/invoke.texi: Document default code model for 64-bit Linux.
index 7688439aab70d299708ad656af02dbbc9fda2b23..0448b2127bed1d3770fba5b7f01fe03342e97705 100644 (file)
@@ -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,24 +1707,63 @@ 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 (TOUPPER (dir.specifier) == 'A')
     {
+      /* Get the number of fractional decimal digits needed to represent
+        the argument without a loss of accuracy.  */
+      tree type = arg ? TREE_TYPE (arg) :
+       (dir.modifier == FMT_LEN_L || dir.modifier == FMT_LEN_ll
+        ? long_double_type_node : double_type_node);
+
+      unsigned fmtprec
+       = REAL_MODE_FORMAT (TYPE_MODE (type))->p;
+
+      /* The precision of the IEEE 754 double format is 53.
+        The precision of all other GCC binary double formats
+        is 56 or less.  */
+      unsigned maxprec = fmtprec <= 56 ? 13 : 15;
+
       /* For %a, leave the minimum precision unspecified to let
         MFPR trim trailing zeros (as it and many other systems
         including Glibc happen to do) and set the maximum
         precision to reflect what it would be with trailing zeros
         present (as Solaris and derived systems do).  */
-      if (prec[0] < 0)
-       prec[0] = -1;
-      if (prec[1] < 0)
+      if (dir.prec[1] < 0)
        {
-          unsigned fmtprec
-           = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)))->p;
-
-              /* The precision of the IEEE 754 double format is 53.
-            The precision of all other GCC binary double formats
-            is 56 or less.  */
-         prec[1] = fmtprec <= 56 ? 13 : 15;
+         /* Both bounds are negative implies that precision has
+            not been specified.  */
+         prec[0] = maxprec;
+         prec[1] = -1;
+       }
+      else if (dir.prec[0] < 0)
+       {
+         /* With a negative lower bound and a non-negative upper
+            bound set the minimum precision to zero and the maximum
+            to the greater of the maximum precision (i.e., with
+            trailing zeros present) and the specified upper bound.  */
+         prec[0] = 0;
+         prec[1] = dir.prec[1] < maxprec ? maxprec : dir.prec[1];
+       }
+    }
+  else if (dir.prec[0] < 0)
+    {
+      if (dir.prec[1] < 0)
+       {
+         /* A precision in a strictly negative range is ignored and
+            the default of 6 is used instead.  */
+         prec[0] = prec[1] = 6;
+       }
+      else
+       {
+         /* For a precision in a partly negative range, the lower bound
+            must be assumed to be zero and the new upper bound is the
+            greater of 6 (the default precision used when the specified
+            precision is negative) and the upper bound of the specified
+            range.  */
+         prec[0] = 0;
+         prec[1] = dir.prec[1] < 6 ? 6 : dir.prec[1];
        }
     }
 
@@ -1734,12 +1823,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))
index 2b73362781cc947596aac92afae09df2fab4ac23..59264c2d4ef8762bc1303e3df1d704676f695f91 100644 (file)
@@ -1,3 +1,11 @@
+2017-03-01  Martin Sebor  <msebor@redhat.com>
+
+       PR middle-end/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.
+       * gcc.dg/tree-ssa/builtin-snprintf-3.c: Ditto.
+
 2017-03-01  Uros Bizjak  <ubizjak@gmail.com>
 
        * gcc.target/i386/invsize-2.c: New test.
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-3.c
new file mode 100644 (file)
index 0000000..e481955
--- /dev/null
@@ -0,0 +1,77 @@
+/* Verify the lower and upper bounds of floating directives with
+   precision whose range crosses zero.
+  { do-do compile }
+  { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+static const double x = 1.23456789;
+
+/* All calls to failure_range must be eliminated.  */
+extern void failure_range (int, int, int);
+
+/* All calls to verify_{lo,hi}_bound must be retained.  */
+extern void verify_lo_bound (int, int);
+extern void verify_hi_bound (int, int);
+
+int test_a (int p)
+{
+  if (p < -1 || 3 < p)
+    p = -1;
+
+  int n = __builtin_snprintf (0, 0, "%.*A", p, x);
+  if (n < 6 || 25 < n)
+    failure_range ('A', 6, 25);
+
+  if (n == 6) verify_lo_bound ('A', 6);
+  if (n == 25) verify_hi_bound ('A', 25);
+
+  return n;
+}
+
+int test_e (int p)
+{
+  if (p < -1 || 3 < p)
+    p = -1;
+
+  int n = __builtin_snprintf (0, 0, "%.*E", p, x);
+  if (n < 5 || 17 < n)
+    failure_range ('E', 5, 17);
+
+  if (n == 5) verify_lo_bound ('E', 5);
+  if (n == 17) verify_hi_bound ('E', 17);
+
+  return n;
+}
+
+int test_f (int p)
+{
+  if (p < -1 || 3 < p)
+    p = -1;
+
+  int n = __builtin_snprintf (0, 0, "%.*F", p, x);
+  if (n < 1 || 13 < n)
+    failure_range ('F', 1, 13);
+
+  if (n == 1) verify_lo_bound ('F', 1);
+  if (n == 13) verify_hi_bound ('F', 13);
+
+  return n;
+}
+
+int test_g (int p)
+{
+  if (p < -1 || 3 < p)
+    p = -1;
+
+  int n = __builtin_snprintf (0, 0, "%.*G", p, x);
+  if (n < 1 || 12 < n)
+    failure_range ('G', 1, 12);
+
+  if (n == 1) verify_lo_bound ('G', 1);
+  if (n == 12) verify_hi_bound ('G', 12);
+
+  return n;
+}
+
+/* { dg-final { scan-tree-dump-times "snprintf" 4 "optimized"} }
+   { dg-final { scan-tree-dump-not "failure_range" "optimized"} }
+   { dg-final { scan-tree-dump-times "verify_" 8 "optimized"} } */
index b873a0c443c52ca1b533c3f5577d1e03ad7eaf2a..8a13f33d2a1e9ef25b2472835f2bb320a0d051c0 100644 (file)
@@ -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" } } */
index 5523acd7db176ffc6026e4192a641f540dfa2bf5..1213e89f7bb0b2c19d715c81a5cef0f07830364e 100644 (file)
@@ -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 (file)
index 0000000..c38a656
--- /dev/null
@@ -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);
+}