aboutsummaryrefslogtreecommitdiff
path: root/gcc/gimple-ssa-sprintf.c
diff options
context:
space:
mode:
authorMartin Sebor <msebor@redhat.com>2016-11-28 21:41:41 +0000
committerMartin Sebor <msebor@gcc.gnu.org>2016-11-28 14:41:41 -0700
commitde6aa93370b68a9fbcf874de8a97c5d1469b7d46 (patch)
tree40efd43f50949c76463ec5e2f1c7e062a2449dc9 /gcc/gimple-ssa-sprintf.c
parentb3a5bff4d709c8b8c6492747ef8104fb88a481b3 (diff)
downloadgcc-de6aa93370b68a9fbcf874de8a97c5d1469b7d46.zip
gcc-de6aa93370b68a9fbcf874de8a97c5d1469b7d46.tar.gz
gcc-de6aa93370b68a9fbcf874de8a97c5d1469b7d46.tar.bz2
PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length...
PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length warning with non-constant width or precision PR middle-end/78520 - missing warning for snprintf with size greater than INT_MAX gcc/ChangeLog: PR middle-end/78520 * gimple-ssa-sprintf.c (target_max_value): Remove. (target_int_max, target_size_max): Use TYPE_MAX_VALUE. (get_width_and_precision): New function. (format_integer, format_floating, get_string_length, format_string): Correct handling of width and precision with unknown value. (format_directive): Add warning. (pass_sprintf_length::compute_format_length): Allow for precision to consist of a sole period with no asterisk or digits after it. gcc/testsuite/ChangeLog: PR middle-end/78520 * gcc.dg/tree-ssa/builtin-sprintf-5.c: Add test cases. * gcc.dg/tree-ssa/builtin-sprintf-6.c: New test. * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Add test cases. * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Add test cases. From-SVN: r242935
Diffstat (limited to 'gcc/gimple-ssa-sprintf.c')
-rw-r--r--gcc/gimple-ssa-sprintf.c353
1 files changed, 229 insertions, 124 deletions
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 71014eb..43bc560 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -226,24 +226,10 @@ struct format_result
/* Return the value of INT_MIN for the target. */
-static HOST_WIDE_INT
+static inline HOST_WIDE_INT
target_int_min ()
{
- const unsigned HOST_WIDE_INT int_min
- = HOST_WIDE_INT_M1U << (TYPE_PRECISION (integer_type_node) - 1);
-
- return int_min;
-}
-
-/* Return the largest value for TYPE on the target. */
-
-static unsigned HOST_WIDE_INT
-target_max_value (tree type)
-{
- const unsigned HOST_WIDE_INT max_value
- = HOST_WIDE_INT_M1U >> (HOST_BITS_PER_WIDE_INT
- - TYPE_PRECISION (type) + 1);
- return max_value;
+ return tree_to_shwi (TYPE_MIN_VALUE (integer_type_node));
}
/* Return the value of INT_MAX for the target. */
@@ -251,7 +237,7 @@ target_max_value (tree type)
static inline unsigned HOST_WIDE_INT
target_int_max ()
{
- return target_max_value (integer_type_node);
+ return tree_to_uhwi (TYPE_MAX_VALUE (integer_type_node));
}
/* Return the value of SIZE_MAX for the target. */
@@ -259,7 +245,7 @@ target_int_max ()
static inline unsigned HOST_WIDE_INT
target_size_max ()
{
- return target_max_value (size_type_node);
+ return tree_to_uhwi (TYPE_MAX_VALUE (size_type_node));
}
/* Return the constant initial value of DECL if available or DECL
@@ -845,6 +831,43 @@ format_pointer (const conversion_spec &spec, tree arg)
return res;
}
+/* Set *PWIDTH and *PPREC according to the width and precision specified
+ in SPEC. Each is set to HOST_WIDE_INT_MIN when the corresponding
+ field is specified but unknown, to zero for width and -1 for precision,
+ respectively when it's not specified, or to a non-negative value
+ corresponding to the known value. */
+static void
+get_width_and_precision (const conversion_spec &spec,
+ HOST_WIDE_INT *pwidth, HOST_WIDE_INT *pprec)
+{
+ HOST_WIDE_INT width = spec.have_width ? spec.width : 0;
+ HOST_WIDE_INT prec = spec.have_precision ? spec.precision : -1;
+
+ if (spec.star_width)
+ {
+ if (TREE_CODE (spec.star_width) == INTEGER_CST)
+ width = abs (tree_to_shwi (spec.star_width));
+ else
+ width = HOST_WIDE_INT_MIN;
+ }
+
+ if (spec.star_precision)
+ {
+ if (TREE_CODE (spec.star_precision) == INTEGER_CST)
+ {
+ prec = tree_to_shwi (spec.star_precision);
+ if (prec < 0)
+ prec = 0;
+ }
+ else
+ prec = HOST_WIDE_INT_MIN;
+ }
+
+ *pwidth = width;
+ *pprec = prec;
+}
+
+
/* Return a range representing the minimum and maximum number of bytes
that the conversion specification SPEC will write on output for the
integer argument ARG when non-null. ARG may be null (for vararg
@@ -855,18 +878,11 @@ format_integer (const conversion_spec &spec, tree arg)
{
tree intmax_type_node;
tree uintmax_type_node;
- /* Set WIDTH and PRECISION to either the values in the format
- specification or to zero. */
- int width = spec.have_width ? spec.width : 0;
- int prec = spec.have_precision ? spec.precision : 0;
- if (spec.star_width)
- width = (TREE_CODE (spec.star_width) == INTEGER_CST
- ? tree_to_shwi (spec.star_width) : 0);
-
- if (spec.star_precision)
- prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
- ? tree_to_shwi (spec.star_precision) : 0);
+ /* Set WIDTH and PRECISION based on the specification. */
+ HOST_WIDE_INT width;
+ HOST_WIDE_INT prec;
+ get_width_and_precision (spec, &width, &prec);
bool sign = spec.specifier == 'd' || spec.specifier == 'i';
@@ -936,15 +952,8 @@ format_integer (const conversion_spec &spec, tree arg)
}
else if (TREE_CODE (arg) == INTEGER_CST)
{
- /* The minimum and maximum number of bytes produced by
- the directive. */
- fmtresult res;
-
/* When a constant argument has been provided use its value
rather than type to determine the length of the output. */
- res.bounded = true;
- res.constant = true;
- res.knownrange = true;
/* Base to format the number in. */
int base;
@@ -977,25 +986,56 @@ format_integer (const conversion_spec &spec, tree arg)
gcc_unreachable ();
}
- /* Convert the argument to the type of the directive. */
- arg = fold_convert (dirtype, arg);
+ int len;
+
+ if ((prec == HOST_WIDE_INT_MIN || prec == 0) && integer_zerop (arg))
+ {
+ /* As a special case, a precision of zero with an argument
+ of zero results in zero bytes regardless of flags (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. */
+ len = 0;
+ }
+ else
+ {
+ /* Convert the argument to the type of the directive. */
+ arg = fold_convert (dirtype, arg);
- maybesign |= spec.get_flag ('+');
+ maybesign |= spec.get_flag ('+');
- /* True when a conversion is preceded by a prefix indicating the base
- of the argument (octal or hexadecimal). */
- bool maybebase = spec.get_flag ('#');
- int len = tree_digits (arg, base, maybesign, maybebase);
+ /* True when a conversion is preceded by a prefix indicating the base
+ of the argument (octal or hexadecimal). */
+ bool maybebase = spec.get_flag ('#');
+ len = tree_digits (arg, base, maybesign, maybebase);
- if (len < prec)
- len = prec;
+ if (len < prec)
+ len = prec;
+ }
if (len < width)
len = width;
- res.range.max = len;
- res.range.min = res.range.max;
- res.bounded = true;
+ /* The minimum and maximum number of bytes produced by the directive. */
+ fmtresult res;
+
+ res.range.min = len;
+
+ /* The upper bound of the number of bytes is unlimited when either
+ width or precision is specified but its value is unknown, and
+ the same as the lower bound otherwise. */
+ if (width == HOST_WIDE_INT_MIN || prec == HOST_WIDE_INT_MIN)
+ {
+ res.range.max = HOST_WIDE_INT_MAX;
+ }
+ else
+ {
+ res.range.max = len;
+ res.bounded = true;
+ res.constant = true;
+ res.knownrange = true;
+ res.bounded = true;
+ }
return res;
}
@@ -1106,8 +1146,10 @@ format_integer (const conversion_spec &spec, tree arg)
or one whose value range cannot be determined, create a T_MIN
constant if the argument's type is signed and T_MAX otherwise,
and use those to compute the range of bytes that the directive
- can output. */
- argmin = build_int_cst (argtype, 1);
+ can output. When precision is specified but unknown, use zero
+ as the minimum since it results in no bytes on output (unless
+ width is specified to be greater than 0). */
+ argmin = build_int_cst (argtype, prec != HOST_WIDE_INT_MIN);
int typeprec = TYPE_PRECISION (dirtype);
int argprec = TYPE_PRECISION (argtype);
@@ -1257,11 +1299,13 @@ format_floating (const conversion_spec &spec, int width, int prec)
{
/* The minimum output is "0x.p+0". */
res.range.min = 6 + (prec > 0 ? prec : 0);
- res.range.max = format_floating_max (type, 'a', prec);
+ res.range.max = (width == INT_MIN
+ ? HOST_WIDE_INT_MAX
+ : format_floating_max (type, 'a', prec));
/* The output of "%a" is fully specified only when precision
- is explicitly specified. */
- res.bounded = -1 < prec;
+ is explicitly specified and width isn't unknown. */
+ res.bounded = INT_MIN != width && -1 < prec;
break;
}
@@ -1274,13 +1318,16 @@ format_floating (const conversion_spec &spec, int width, int prec)
res.range.min = (sign
+ 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+ 2 /* e+ */ + 2);
- /* The maximum output is the minimum plus sign (unless already
- included), plus the difference between the minimum exponent
- of 2 and the maximum exponent for the type. */
- res.range.max = res.range.min + !sign + logexpdigs - 2;
-
- /* "%e" is fully specified and the range of bytes is bounded. */
- res.bounded = true;
+ /* Unless width is uknown the maximum output is the minimum plus
+ sign (unless already included), plus the difference between
+ the minimum exponent of 2 and the maximum exponent for the type. */
+ res.range.max = (width == INT_MIN
+ ? HOST_WIDE_INT_M1U
+ : res.range.min + !sign + logexpdigs - 2);
+
+ /* "%e" is fully specified and the range of bytes is bounded
+ unless width is unknown. */
+ res.bounded = INT_MIN != width;
break;
}
@@ -1296,10 +1343,11 @@ format_floating (const conversion_spec &spec, int width, int prec)
format_floating_max (double_type_node, 'f'),
format_floating_max (long_double_type_node, 'f')
};
- res.range.max = f_max [ldbl];
+ res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : f_max [ldbl];
- /* "%f" is fully specified and the range of bytes is bounded. */
- res.bounded = true;
+ /* "%f" is fully specified and the range of bytes is bounded
+ unless width is unknown. */
+ res.bounded = INT_MIN != width;
break;
}
case 'G':
@@ -1313,10 +1361,11 @@ format_floating (const conversion_spec &spec, int width, int prec)
format_floating_max (double_type_node, 'g'),
format_floating_max (long_double_type_node, 'g')
};
- res.range.max = g_max [ldbl];
+ res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : g_max [ldbl];
- /* "%g" is fully specified and the range of bytes is bounded. */
- res.bounded = true;
+ /* "%g" is fully specified and the range of bytes is bounded
+ unless width is unknown. */
+ res.bounded = INT_MIN != width;
break;
}
@@ -1342,6 +1391,9 @@ format_floating (const conversion_spec &spec, int width, int prec)
static fmtresult
format_floating (const conversion_spec &spec, tree arg)
{
+ /* 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;
@@ -1354,12 +1406,13 @@ format_floating (const conversion_spec &spec, tree arg)
else if (spec.star_width)
{
if (TREE_CODE (spec.star_width) == INTEGER_CST)
- width = tree_to_shwi (spec.star_width);
- else
{
- res.range.min = res.range.max = HOST_WIDE_INT_M1U;
- return res;
+ width = tree_to_shwi (spec.star_width);
+ if (width < 0)
+ width = -width;
}
+ else
+ width = INT_MIN;
}
if (spec.have_precision)
@@ -1370,6 +1423,7 @@ format_floating (const conversion_spec &spec, tree arg)
prec = tree_to_shwi (spec.star_precision);
else
{
+ /* FIXME: Handle non-constant precision. */
res.range.min = res.range.max = HOST_WIDE_INT_M1U;
return res;
}
@@ -1409,9 +1463,9 @@ format_floating (const conversion_spec &spec, tree arg)
*pfmt++ = *pf;
/* Append width when specified and precision. */
- if (width != -1)
+ if (-1 < width)
pfmt += sprintf (pfmt, "%i", width);
- if (prec != -1)
+ if (-1 < prec)
pfmt += sprintf (pfmt, ".%i", prec);
/* Append the MPFR 'R' floating type specifier (no length modifier
@@ -1438,16 +1492,24 @@ format_floating (const conversion_spec &spec, tree arg)
*minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval);
}
+ /* The range of output is known even if the result isn't bounded. */
+ if (width == INT_MIN)
+ {
+ res.knownrange = false;
+ res.range.max = HOST_WIDE_INT_MAX;
+ }
+ else
+ res.knownrange = true;
+
/* The output of all directives except "%a" is fully specified
and so the result is bounded unless it exceeds INT_MAX.
For "%a" the output is fully specified only when precision
is explicitly specified. */
- res.bounded = ((TOUPPER (spec.specifier) != 'A'
- || (0 <= prec && (unsigned) prec < target_int_max ()))
+ res.bounded = (res.knownrange
+ && (TOUPPER (spec.specifier) != 'A'
+ || (0 <= prec && (unsigned) prec < target_int_max ()))
&& res.range.min < target_int_max ());
- /* The range of output is known even if the result isn't bounded. */
- res.knownrange = true;
return res;
}
@@ -1517,20 +1579,10 @@ get_string_length (tree str)
static fmtresult
format_string (const conversion_spec &spec, tree arg)
{
- unsigned width = spec.have_width && spec.width > 0 ? spec.width : 0;
- int prec = spec.have_precision ? spec.precision : -1;
-
- if (spec.star_width)
- {
- width = (TREE_CODE (spec.star_width) == INTEGER_CST
- ? tree_to_shwi (spec.star_width) : 0);
- if (width > INT_MAX)
- width = 0;
- }
-
- if (spec.star_precision)
- prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
- ? tree_to_shwi (spec.star_precision) : -1);
+ /* Set WIDTH and PRECISION based on the specification. */
+ HOST_WIDE_INT width;
+ HOST_WIDE_INT prec;
+ get_width_and_precision (spec, &width, &prec);
fmtresult res;
@@ -1590,11 +1642,12 @@ format_string (const conversion_spec &spec, tree arg)
res.range = slen.range;
/* The output of "%s" and "%ls" directives with a constant
- string is in a known range. For "%s" it is the length
- of the string. For "%ls" it is in the range [length,
- length * MB_LEN_MAX]. (The final range can be further
- constrained by width and precision but it's always known.) */
- res.knownrange = true;
+ string is in a known range unless width of an unknown value
+ is specified. For "%s" it is the length of the string. For
+ "%ls" it is in the range [length, length * MB_LEN_MAX].
+ (The final range can be further constrained by width and
+ precision but it's always known.) */
+ res.knownrange = -1 < width;
if (spec.modifier == FMT_LEN_l)
{
@@ -1622,19 +1675,32 @@ format_string (const conversion_spec &spec, tree arg)
if (0 <= prec)
res.range.max = prec;
}
- else
+ else if (0 <= width)
{
- /* The output od a "%s" directive with a constant argument
- is bounded, constant, and obviously in a known range. */
+ /* The output of a "%s" directive with a constant argument
+ and constant or no width is bounded. It is constant if
+ precision is either not specified or it is specified and
+ its value is known. */
res.bounded = true;
- res.constant = true;
+ res.constant = prec != HOST_WIDE_INT_MIN;
+ }
+ else if (width == HOST_WIDE_INT_MIN)
+ {
+ /* Specified but unknown width makes the output unbounded. */
+ res.range.max = HOST_WIDE_INT_MAX;
}
- if (0 <= prec && (unsigned)prec < res.range.min)
+ if (0 <= prec && (unsigned HOST_WIDE_INT)prec < res.range.min)
{
res.range.min = prec;
res.range.max = prec;
}
+ else if (prec == HOST_WIDE_INT_MIN)
+ {
+ /* When precision is specified but not known the lower
+ bound is assumed to be as low as zero. */
+ res.range.min = 0;
+ }
}
else
{
@@ -1648,10 +1714,10 @@ format_string (const conversion_spec &spec, tree arg)
{
if (slen.range.min >= target_int_max ())
slen.range.min = 0;
- else if ((unsigned)prec < slen.range.min)
+ else if ((unsigned HOST_WIDE_INT)prec < slen.range.min)
slen.range.min = prec;
- if ((unsigned)prec < slen.range.max
+ if ((unsigned HOST_WIDE_INT)prec < slen.range.max
|| slen.range.max >= target_int_max ())
slen.range.max = prec;
}
@@ -1674,20 +1740,23 @@ format_string (const conversion_spec &spec, tree arg)
}
/* Adjust the lengths for field width. */
- if (res.range.min < width)
- res.range.min = width;
+ if (0 < width)
+ {
+ if (res.range.min < (unsigned HOST_WIDE_INT)width)
+ res.range.min = width;
- if (res.range.max < width)
- res.range.max = width;
+ if (res.range.max < (unsigned HOST_WIDE_INT)width)
+ res.range.max = width;
- /* Adjust BOUNDED if width happens to make them equal. */
- if (res.range.min == res.range.max && res.range.min < target_int_max ()
- && bounded)
- res.bounded = true;
+ /* Adjust BOUNDED if width happens to make them equal. */
+ if (res.range.min == res.range.max && res.range.min < target_int_max ()
+ && bounded)
+ res.bounded = true;
+ }
/* When precision is specified the range of characters on output
is known to be bounded by it. */
- if (-1 < prec)
+ if (-1 < width && -1 < prec)
res.knownrange = true;
return res;
@@ -1803,7 +1872,7 @@ format_directive (const pass_sprintf_length::call_info &info,
(int)cvtlen, cvtbeg, fmtres.range.min,
navail);
}
- else
+ else if (fmtres.range.max < HOST_WIDE_INT_MAX)
{
const char* fmtstr
= (info.bounded
@@ -1817,6 +1886,19 @@ format_directive (const pass_sprintf_length::call_info &info,
(int)cvtlen, cvtbeg,
fmtres.range.min, fmtres.range.max, navail);
}
+ else
+ {
+ const char* fmtstr
+ = (info.bounded
+ ? G_("%<%.*s%> directive output truncated writing "
+ "%wu or more bytes into a region of size %wu")
+ : G_("%<%.*s%> directive writing %wu or more bytes "
+ "into a region of size %wu"));
+ warned = fmtwarn (dirloc, pargrange, NULL,
+ OPT_Wformat_length_, fmtstr,
+ (int)cvtlen, cvtbeg,
+ fmtres.range.min, navail);
+ }
}
else if (navail < fmtres.range.max
&& (((spec.specifier == 's'
@@ -2273,13 +2355,22 @@ pass_sprintf_length::compute_format_length (const call_info &info,
if (dollar || !spec.star_width)
{
- if (spec.have_width && spec.width == 0)
+ if (spec.have_width)
{
- /* The '0' that has been interpreted as a width above is
- actually a flag. Reset HAVE_WIDTH, set the '0' flag,
- and continue processing other flags. */
- spec.have_width = false;
- spec.set_flag ('0');
+ if (spec.width == 0)
+ {
+ /* The '0' that has been interpreted as a width above is
+ actually a flag. Reset HAVE_WIDTH, set the '0' flag,
+ and continue processing other flags. */
+ spec.have_width = false;
+ spec.set_flag ('0');
+ }
+ else if (!dollar)
+ {
+ /* (Non-zero) width has been seen. The next character
+ is either a period or a digit. */
+ goto start_precision;
+ }
}
/* When either '$' has been seen, or width has not been seen,
the next field is the optional flags followed by an optional
@@ -2324,6 +2415,7 @@ pass_sprintf_length::compute_format_length (const call_info &info,
}
}
+ start_precision:
if ('.' == *pf)
{
++pf;
@@ -2341,7 +2433,12 @@ pass_sprintf_length::compute_format_length (const call_info &info,
++pf;
}
else
- return;
+ {
+ /* The decimal precision or the asterisk are optional.
+ When neither is specified it's taken to be zero. */
+ spec.precision = 0;
+ spec.have_precision = true;
+ }
}
switch (*pf)
@@ -2701,9 +2798,9 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
if (idx_dstsize == HOST_WIDE_INT_M1U)
{
- // For non-bounded functions like sprintf, to determine
- // the size of the destination from the object or pointer
- // passed to it as the first argument.
+ /* For non-bounded functions like sprintf, determine the size
+ of the destination from the object or pointer passed to it
+ as the first argument. */
dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0));
}
else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize))
@@ -2715,10 +2812,18 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
dstsize = tree_to_uhwi (size);
/* No object can be larger than SIZE_MAX bytes (half the address
space) on the target. This imposes a limit that's one byte
- less than that. */
+ less than that.
+ The functions are defined only for output of at most INT_MAX
+ bytes. Specifying a bound in excess of that limit effectively
+ defeats the bounds checking (and on some implementations such
+ as Solaris cause the function to fail with EINVAL). */
if (dstsize >= target_size_max () / 2)
warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
- "specified destination size %wu too large",
+ "specified destination size %wu is too large",
+ dstsize);
+ else if (dstsize > target_int_max ())
+ warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+ "specified destination size %wu exceeds %<INT_MAX %>",
dstsize);
}
else if (TREE_CODE (size) == SSA_NAME)