PR middle-end/78786 - GCC hangs/out of memory calling sprintf with large precision
gcc/ChangeLog:
PR middle-end/78786
* gimple-ssa-sprintf.c (target_dir_max): New macro.
(get_mpfr_format_length): New function.
(format_integer): Use HOST_WIDE_INT instead of int.
(format_floating_max): Same.
(format_floating): Call get_mpfr_format_length.
(format_directive): Use target_dir_max.
gcc/testsuite/ChangeLog:
PR middle-end/78786
* gcc.dg/tree-ssa/builtin-sprintf-warn-7.c: New test.
===================================================================
@@ -84,6 +84,11 @@ along with GCC; see the file COPYING3. If not see
to be used for optimization but it's good enough as is for warnings. */
#define target_mb_len_max 6
+/* The maximum number of bytes a single non-string directive can result
+ in. This is the result of printf("%.*Lf", INT_MAX, -LDBL_MAX) for
+ LDBL_MAX_10_EXP of 4932. */
+#define target_dir_max() (target_int_max () + 4932 + 2)
+
namespace {
const pass_data pass_data_sprintf_length = {
@@ -989,7 +994,7 @@ format_integer (const conversion_spec &spec, tree
gcc_unreachable ();
}
- int len;
+ HOST_WIDE_INT len;
if ((prec == HOST_WIDE_INT_MIN || prec == 0) && integer_zerop (arg))
{
@@ -1214,11 +1219,73 @@ format_integer (const conversion_spec &spec, tree
return res;
}
+/* Return the number of bytes that a format directive consisting of FLAGS,
+ PRECision, format SPECification, and MPFR rounding specifier RNDSPEC,
+ would result for argument X under ideal conditions (i.e., if PREC
+ weren't excessive). MPFR 3.1 allocates large amounts of memory for
+ values of PREC with large magnitude and can fail (see MPFR bug #21056).
+ This function works around those problems. */
+
+static unsigned HOST_WIDE_INT
+get_mpfr_format_length (mpfr_ptr x, const char *flags, HOST_WIDE_INT prec,
+ char spec, char rndspec)
+{
+ char fmtstr[40];
+
+ HOST_WIDE_INT len = strlen (flags);
+
+ fmtstr[0] = '%';
+ memcpy (fmtstr + 1, flags, len);
+ memcpy (fmtstr + 1 + len, ".*R", 3);
+ fmtstr[len + 4] = rndspec;
+ fmtstr[len + 5] = spec;
+ fmtstr[len + 6] = '\0';
+
+ /* Avoid passing negative precisions with larger magnitude to MPFR
+ to avoid exposing its bugs. (A negative precision is supposed
+ to be ignored.) */
+ if (prec < 0)
+ prec = -1;
+
+ HOST_WIDE_INT p = prec;
+
+ if (TOUPPER (spec) == 'G')
+ {
+ /* 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. */
+ if (9864 < prec)
+ prec = 9864;
+ p = prec;
+ }
+ else
+ {
+ /* Cap precision arbitrarily at 1KB and add the difference
+ (if any) to the MPFR result. */
+ if (1024 < prec)
+ p = 1024;
+ }
+
+ len = mpfr_snprintf (NULL, 0, fmtstr, (int)p, x);
+
+ /* Handle the unlikely (impossible?) error by returning more than
+ the maximum dictated by the function's return type. */
+ if (len < 0)
+ return target_dir_max () + 1;
+
+ /* Adjust the retrun value by the difference. */
+ if (p < prec)
+ len += prec - p;
+
+ return len;
+}
+
/* Return the number of bytes to format using the format specifier
SPEC the largest value in the real floating TYPE. */
-static int
-format_floating_max (tree type, char spec, int prec = -1)
+static unsigned HOST_WIDE_INT
+format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
{
machine_mode mode = TYPE_MODE (type);
@@ -1243,21 +1310,8 @@ format_integer (const conversion_spec &spec, tree
mpfr_init2 (x, rfmt->p);
mpfr_from_real (x, &rv, GMP_RNDN);
- int n;
-
- if (-1 < prec)
- {
- const char fmt[] = { '%', '.', '*', 'R', spec, '\0' };
- n = mpfr_snprintf (NULL, 0, fmt, prec, x);
- }
- else
- {
- const char fmt[] = { '%', 'R', spec, '\0' };
- n = mpfr_snprintf (NULL, 0, fmt, x);
- }
-
/* Return a value one greater to account for the leading minus sign. */
- return n + 1;
+ return 1 + get_mpfr_format_length (x, "", prec, spec, 'D');
}
/* Return a range representing the minimum and maximum number of bytes
@@ -1266,7 +1320,8 @@ format_integer (const conversion_spec &spec, tree
is used when the directive argument or its value isn't known. */
static fmtresult
-format_floating (const conversion_spec &spec, int width, int prec)
+format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
+ HOST_WIDE_INT prec)
{
tree type;
bool ldbl = false;
@@ -1357,7 +1412,7 @@ static fmtresult
res.range.min = 2 + (prec < 0 ? 6 : prec);
/* Compute the maximum just once. */
- const int f_max[] = {
+ const HOST_WIDE_INT f_max[] = {
format_floating_max (double_type_node, 'f', prec),
format_floating_max (long_double_type_node, 'f', prec)
};
@@ -1372,10 +1427,10 @@ static fmtresult
case 'g':
{
/* The minimum is the same as for '%F'. */
- res.range.min = 2 + (prec < 0 ? 6 : prec);
+ res.range.min = 1;
/* Compute the maximum just once. */
- const int g_max[] = {
+ const HOST_WIDE_INT g_max[] = {
format_floating_max (double_type_node, 'g', prec),
format_floating_max (long_double_type_node, 'g', prec)
};
@@ -1412,8 +1467,8 @@ format_floating (const conversion_spec &spec, tree
/* Set WIDTH to -1 when it's not specified, to INT_MIN when it is
specified by the asterisk to an unknown value, and otherwise to
a non-negative value corresponding to the specified width. */
- int width = -1;
- int prec = -1;
+ HOST_WIDE_INT width = -1;
+ HOST_WIDE_INT prec = -1;
/* The minimum and maximum number of bytes produced by the directive. */
fmtresult res;
@@ -1473,7 +1528,6 @@ format_floating (const conversion_spec &spec, tree
char fmtstr [40];
char *pfmt = fmtstr;
- *pfmt++ = '%';
/* Append flags. */
for (const char *pf = "-+ #0"; *pf; ++pf)
@@ -1480,22 +1534,6 @@ format_floating (const conversion_spec &spec, tree
if (spec.get_flag (*pf))
*pfmt++ = *pf;
- /* Append width when specified and precision. */
- if (-1 < width)
- pfmt += sprintf (pfmt, "%i", width);
- if (-1 < prec)
- pfmt += sprintf (pfmt, ".%i", prec);
-
- /* Append the MPFR 'R' floating type specifier (no length modifier
- is necessary or allowed by MPFR for mpfr_t values). */
- *pfmt++ = 'R';
-
- /* Save the position of the MPFR rounding specifier and skip over
- it. It will be set in each iteration in the loop below. */
- char* const rndspec = pfmt++;
-
- /* Append the C type specifier and nul-terminate. */
- *pfmt++ = spec.specifier;
*pfmt = '\0';
for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
@@ -1503,11 +1541,17 @@ format_floating (const conversion_spec &spec, tree
/* Use the MPFR rounding specifier to round down in the first
iteration and then up. In most but not all cases this will
result in the same number of bytes. */
- *rndspec = "DU"[i];
+ char rndspec = "DU"[i];
- /* Format it and store the result in the corresponding
- member of the result struct. */
- *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval);
+ /* Format it and store the result in the corresponding member
+ of the result struct. */
+ unsigned HOST_WIDE_INT len
+ = get_mpfr_format_length (mpfrval, fmtstr, prec,
+ spec.specifier, rndspec);
+ if (0 < width && len < (unsigned)width)
+ len = width;
+
+ *minmax[i] = len;
}
/* The range of output is known even if the result isn't bounded. */
@@ -1834,9 +1878,13 @@ format_directive (const pass_sprintf_length::call_
if (!fmtres.knownrange)
{
/* Only when the range is known, check it against the host value
- of INT_MAX. Otherwise the range doesn't correspond to known
- values of the argument. */
- if (fmtres.range.max >= target_int_max ())
+ of INT_MAX + (the number of bytes of the "%.*Lf" directive with
+ INT_MAX precision, which is the longest possible output of any
+ single directive). That's the largest valid byte count (though
+ not valid call to a printf-like function because it can never
+ return such a count). Otherwise, the range doesn't correspond
+ to known values of the argument. */
+ if (fmtres.range.max > target_dir_max ())
{
/* Normalize the MAX counter to avoid having to deal with it
later. The counter can be less than HOST_WIDE_INT_M1U
@@ -1850,7 +1898,7 @@ format_directive (const pass_sprintf_length::call_
res->number_chars = HOST_WIDE_INT_M1U;
}
- if (fmtres.range.min >= target_int_max ())
+ if (fmtres.range.min > target_dir_max ())
{
/* Disable exact length checking after a failure to determine
even the minimum number of characters (it shouldn't happen
===================================================================
@@ -0,0 +1,183 @@
+/* PR middle-end/78786 - GCC hangs/out of memory calling sprintf with large
+ precision
+ { dg-do compile }
+ { dg-require-effective-target int32plus }
+ { dg-options "-Wformat-length -ftrack-macro-expansion=0" } */
+
+#define INT_MAX __INT_MAX__
+#define INT_MIN (-INT_MAX - 1)
+
+typedef __SIZE_TYPE__ size_t;
+
+void sink (int, void*);
+
+char buf [1];
+
+#define T(n, fmt, ...) \
+ sink (__builtin_sprintf (buf + sizeof buf - n, fmt, __VA_ARGS__), buf)
+
+void test_integer_cst (void)
+{
+ T (0, "%*d", INT_MIN, 0); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*d", INT_MAX, 0); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*d", INT_MIN, 0); /* { dg-warning "writing 1 byte" } */
+ T (0, "%.*d", INT_MAX, 0); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%*.*d", INT_MIN, INT_MIN, 0); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*d", INT_MAX, INT_MAX, 0); /* { dg-warning "writing 2147483647 bytes" } */
+}
+
+void test_integer_var (int i)
+{
+ T (0, "%*d", INT_MIN, i); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*d", INT_MAX, i); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*d", INT_MIN, i); /* { dg-warning "writing between 1 and 11 bytes" } */
+ T (0, "%.*d", INT_MAX, i); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%*.*d", INT_MIN, INT_MIN, i); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*d", INT_MAX, INT_MAX, i); /* { dg-warning "writing 2147483647 bytes" } */
+}
+
+void test_floating_a_cst (void)
+{
+ T (0, "%*a", INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*a", INT_MAX, 0.); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*a", INT_MIN, 0.); /* { dg-warning "writing 6 bytes" } */
+
+ T (0, "%.*a", INT_MAX, 0.); /* { dg-warning "writing 2147483654 bytes" } */
+
+ T (0, "%*.*a", INT_MIN, INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*a", INT_MAX, INT_MAX, 0.); /* { dg-warning "writing 2147483654 bytes" } */
+}
+
+void test_floating_a_var (double x)
+{
+ T (0, "%*a", INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*a", INT_MAX, x); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*a", INT_MIN, x); /* { dg-warning "writing between 6 and 24 bytes" } */
+
+ T (0, "%.*a", INT_MAX, x); /* { dg-warning "writing between 2147483653 and 2147483658 bytes" } */
+
+ T (0, "%*.*a", INT_MIN, INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*a", INT_MAX, INT_MAX, x); /* { dg-warning "writing between 2147483653 and 2147483658 bytes" } */
+}
+
+void test_floating_e_cst (void)
+{
+ T (0, "%*e", INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*e", INT_MAX, 0.); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*e", INT_MIN, 0.); /* { dg-warning "writing 5 bytes" } */
+
+ T (0, "%.*e", INT_MAX, 0.); /* { dg-warning "writing 2147483653 bytes" } */
+
+ T (0, "%*.*e", INT_MIN, INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*e", INT_MAX, INT_MAX, 0.); /* { dg-warning "writing 2147483653 bytes" } */
+}
+
+void test_floating_e_var (double x)
+{
+ T (0, "%*e", INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*e", INT_MAX, x); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*e", INT_MIN, x); /* { dg-warning "writing between 12 and 14 bytes" } */
+
+ T (0, "%.*e", INT_MAX, x); /* { dg-warning "writing between 2147483653 and 2147483655 bytes" } */
+
+ T (0, "%*.*e", INT_MIN, INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*e", INT_MAX, INT_MAX, x); /* { dg-warning "writing between 2147483653 and 2147483655 bytes" } */
+}
+
+void test_floating_f_cst (void)
+{
+ T (0, "%*f", INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*f", INT_MAX, 0.); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*f", INT_MIN, 0.); /* { dg-warning "writing 1 byte" } */
+
+ T (0, "%.*f", INT_MAX, 0.); /* { dg-warning "writing 2147483649 bytes" } */
+
+ T (0, "%*.*f", INT_MIN, INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*f", INT_MAX, INT_MAX, 0.); /* { dg-warning "writing 2147483649 bytes" } */
+}
+
+void test_floating_f_var (double x)
+{
+ T (0, "%*f", INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*f", INT_MAX, x); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*f", INT_MIN, x); /* { dg-warning "writing between 8 and 317 bytes" } */
+
+ T (0, "%.*f", INT_MAX, x); /* { dg-warning "writing between 2147483649 and 2147483958 bytes" } */
+
+ T (0, "%*.*f", INT_MIN, INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*f", INT_MAX, INT_MAX, x); /* { dg-warning "writing between 2147483649 and 2147483958 bytes" } */
+}
+
+void test_floating_g_cst (void)
+{
+ T (0, "%*g", INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*g", INT_MAX, 0.); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*g", INT_MIN, 0.); /* { dg-warning "writing 1 byte" } */
+
+ T (0, "%.*g", INT_MAX, 0.); /* { dg-warning "writing 1 byte" } */
+
+ T (0, "%*.*g", INT_MIN, INT_MIN, 0.); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*g", INT_MAX, INT_MAX, 0.); /* { dg-warning "writing 2147483647 bytes" } */
+}
+
+void test_floating_g (double x)
+{
+ T (0, "%*g", INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*g", INT_MAX, x); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*g", INT_MIN, x); /* { dg-warning "writing between 1 and 13 bytes" } */
+
+ T (0, "%.*g", INT_MAX, x); /* { dg-warning "writing between 1 and 310 bytes" } */
+
+ T (0, "%*.*g", INT_MIN, INT_MIN, x); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*g", INT_MAX, INT_MAX, x); /* { dg-warning "writing 2147483647 bytes" } */
+}
+
+void test_string_cst (void)
+{
+ T (0, "%*s", INT_MIN, ""); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*s", INT_MAX, ""); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*s", INT_MIN, ""); /* { dg-warning "writing a terminating nul" } */
+
+ T (0, "%.*s", INT_MAX, ""); /* { dg-warning "writing a terminating nul" } */
+
+ T (0, "%*.*s", INT_MIN, INT_MIN, ""); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*s", INT_MAX, INT_MAX, ""); /* { dg-warning "writing 2147483647 bytes" } */
+}
+
+void test_string_var (const char *s)
+{
+ T (0, "%*s", INT_MIN, s); /* { dg-warning "writing 2147483648 bytes" } */
+ T (0, "%*s", INT_MAX, s); /* { dg-warning "writing 2147483647 bytes" } */
+
+ T (0, "%.*s", INT_MIN, s); /* { dg-warning "writing a terminating nul" } */
+
+ T (0, "%.*s", INT_MAX, s); /* { dg-warning "writing between 0 and 2147483647 bytes" } */
+
+ T (0, "%*.*s", INT_MIN, INT_MIN, s); /* { dg-warning "writing 2147483648 bytes" } */
+
+ T (0, "%*.*s", INT_MAX, INT_MAX, s); /* { dg-warning "writing 2147483647 bytes" } */
+}