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.
@@ -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))
@@ -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" } } */
@@ -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" } */
new file mode 100644
@@ -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);
+}