diff options
author | Martin Sebor <msebor@redhat.com> | 2016-11-28 21:41:41 +0000 |
---|---|---|
committer | Martin Sebor <msebor@gcc.gnu.org> | 2016-11-28 14:41:41 -0700 |
commit | de6aa93370b68a9fbcf874de8a97c5d1469b7d46 (patch) | |
tree | 40efd43f50949c76463ec5e2f1c7e062a2449dc9 /gcc/gimple-ssa-sprintf.c | |
parent | b3a5bff4d709c8b8c6492747ef8104fb88a481b3 (diff) | |
download | gcc-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.c | 353 |
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) |