From 1eb4547b1058d6a4e1912ac0f15cc0a69bf4fd78 Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Mon, 24 Oct 2016 16:53:20 +0000 Subject: [PATCH] PR middle-end/77735 - FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c gcc/ChangeLog: PR middle-end/77735 * builtins.c (string_length): New function. (c_strlen): Use string_length. Correctly handle wide strings. * gimple-ssa-sprintf.c (target_max_value, target_size_max): New functions. (target_int_max): Call target_max_value. (format_result::knownrange): New data member. (fmtresult::fmtresult): Define default constructor. (format_integer): Use it and set format_result::knownrange. Handle global constants. (format_floating_max): Add third argument. (format_floating): Recompute maximum value for %a for each argument. (get_string_length): Use fmtresult default ctor. (format_string): Set format_result::knownrange. (format_directive): Check format_result::knownrange. (add_bytes): Same. Correct caret placement in diagnostics. (pass_sprintf_length::compute_format_length): Set format_result::knownrange. (pass_sprintf_length::handle_gimple_call): Use target_size_max. gcc/testsuite/ChangeLog: PR middle-end/77735 * gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases. * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same. * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same. * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Adjust/relax. * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Add test cases. * gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL for LP64 only. * gcc.dg/tree-ssa/builtin-sprintf.c: Add test cases. From-SVN: r241489 --- gcc/ChangeLog | 22 ++ gcc/builtins.c | 103 ++++-- gcc/gimple-ssa-sprintf.c | 335 ++++++++++++------ gcc/testsuite/ChangeLog | 11 + .../gcc.dg/tree-ssa/builtin-sprintf-2.c | 50 ++- .../gcc.dg/tree-ssa/builtin-sprintf-warn-1.c | 97 ++++- .../gcc.dg/tree-ssa/builtin-sprintf-warn-2.c | 15 +- .../gcc.dg/tree-ssa/builtin-sprintf-warn-3.c | 6 +- .../gcc.dg/tree-ssa/builtin-sprintf-warn-4.c | 94 ++++- .../gcc.dg/tree-ssa/builtin-sprintf-warn-6.c | 2 +- .../gcc.dg/tree-ssa/builtin-sprintf.c | 26 +- 11 files changed, 560 insertions(+), 201 deletions(-) diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 0d981e51ec3..a8528b91c41 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,25 @@ +2016-10-24 Martin Sebor + + PR middle-end/77735 + * builtins.c (string_length): New function. + (c_strlen): Use string_length. Correctly handle wide strings. + * gimple-ssa-sprintf.c (target_max_value, target_size_max): New + functions. + (target_int_max): Call target_max_value. + (format_result::knownrange): New data member. + (fmtresult::fmtresult): Define default constructor. + (format_integer): Use it and set format_result::knownrange. + Handle global constants. + (format_floating_max): Add third argument. + (format_floating): Recompute maximum value for %a for each argument. + (get_string_length): Use fmtresult default ctor. + (format_string): Set format_result::knownrange. + (format_directive): Check format_result::knownrange. + (add_bytes): Same. Correct caret placement in diagnostics. + (pass_sprintf_length::compute_format_length): Set + format_result::knownrange. + (pass_sprintf_length::handle_gimple_call): Use target_size_max. + 2016-10-24 Jakub Jelinek * config/i386/i386.c (ix86_in_large_data_p, ix86_expand_builtin): Use diff --git a/gcc/builtins.c b/gcc/builtins.c index ff377732b9f..997c0e8c1cb 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -503,9 +503,44 @@ get_pointer_alignment (tree exp) return align; } -/* Compute the length of a C string. TREE_STRING_LENGTH is not the right - way, because it could contain a zero byte in the middle. - TREE_STRING_LENGTH is the size of the character array, not the string. +/* Return the number of non-zero elements in the sequence + [ PTR, PTR + MAXELTS ) where each element's size is ELTSIZE bytes. + ELTSIZE must be a power of 2 less than 8. Used by c_strlen. */ + +static unsigned +string_length (const void *ptr, unsigned eltsize, unsigned maxelts) +{ + gcc_checking_assert (eltsize == 1 || eltsize == 2 || eltsize == 4); + + unsigned n; + + if (eltsize == 1) + { + /* Optimize the common case of plain char. */ + for (n = 0; n < maxelts; n++) + { + const char *elt = (const char*) ptr + n; + if (!*elt) + break; + } + } + else + { + for (n = 0; n < maxelts; n++) + { + const char *elt = (const char*) ptr + n * eltsize; + if (!memcmp (elt, "\0\0\0\0", eltsize)) + break; + } + } + return n; +} + +/* Compute the length of a null-terminated character string or wide + character string handling character sizes of 1, 2, and 4 bytes. + TREE_STRING_LENGTH is not the right way because it evaluates to + the size of the character array in bytes (as opposed to characters) + and because it can contain a zero byte in the middle. ONLY_VALUE should be nonzero if the result is not going to be emitted into the instruction stream and zero if it is going to be expanded. @@ -526,12 +561,6 @@ get_pointer_alignment (tree exp) tree c_strlen (tree src, int only_value) { - tree offset_node; - HOST_WIDE_INT offset; - int max; - const char *ptr; - location_t loc; - STRIP_NOPS (src); if (TREE_CODE (src) == COND_EXPR && (only_value || !TREE_SIDE_EFFECTS (TREE_OPERAND (src, 0)))) @@ -548,25 +577,36 @@ c_strlen (tree src, int only_value) && (only_value || !TREE_SIDE_EFFECTS (TREE_OPERAND (src, 0)))) return c_strlen (TREE_OPERAND (src, 1), only_value); - loc = EXPR_LOC_OR_LOC (src, input_location); + location_t loc = EXPR_LOC_OR_LOC (src, input_location); - src = string_constant (src, &offset_node); + /* Offset from the beginning of the string in bytes. */ + tree byteoff; + src = string_constant (src, &byteoff); if (src == 0) return NULL_TREE; - max = TREE_STRING_LENGTH (src) - 1; - ptr = TREE_STRING_POINTER (src); + /* Determine the size of the string element. */ + unsigned eltsize + = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (TREE_TYPE (src)))); + + /* Set MAXELTS to sizeof (SRC) / sizeof (*SRC) - 1, the maximum possible + length of SRC. */ + unsigned maxelts = TREE_STRING_LENGTH (src) / eltsize - 1; - if (offset_node && TREE_CODE (offset_node) != INTEGER_CST) + /* PTR can point to the byte representation of any string type, including + char* and wchar_t*. */ + const char *ptr = TREE_STRING_POINTER (src); + + if (byteoff && TREE_CODE (byteoff) != INTEGER_CST) { /* If the string has an internal zero byte (e.g., "foo\0bar"), we can't compute the offset to the following null if we don't know where to start searching for it. */ - int i; - - for (i = 0; i < max; i++) - if (ptr[i] == 0) + if (string_length (ptr, eltsize, maxelts) < maxelts) + { + /* Return when an embedded null character is found. */ return NULL_TREE; + } /* We don't know the starting offset, but we do know that the string has no internal zero bytes. We can assume that the offset falls @@ -575,27 +615,31 @@ c_strlen (tree src, int only_value) and return that. This would perhaps not be valid if we were dealing with named arrays in addition to literal string constants. */ - return size_diffop_loc (loc, size_int (max), offset_node); + return size_diffop_loc (loc, size_int (maxelts * eltsize), byteoff); } + /* Offset from the beginning of the string in elements. */ + HOST_WIDE_INT eltoff; + /* We have a known offset into the string. Start searching there for a null character if we can represent it as a single HOST_WIDE_INT. */ - if (offset_node == 0) - offset = 0; - else if (! tree_fits_shwi_p (offset_node)) - offset = -1; + if (byteoff == 0) + eltoff = 0; + else if (! tree_fits_shwi_p (byteoff)) + eltoff = -1; else - offset = tree_to_shwi (offset_node); + eltoff = tree_to_shwi (byteoff) / eltsize; /* If the offset is known to be out of bounds, warn, and call strlen at runtime. */ - if (offset < 0 || offset > max) + if (eltoff < 0 || eltoff > maxelts) { /* Suppress multiple warnings for propagated constant strings. */ if (only_value != 2 && !TREE_NO_WARNING (src)) { - warning_at (loc, 0, "offset outside bounds of constant string"); + warning_at (loc, 0, "offset %qwi outside bounds of constant string", + eltoff); TREE_NO_WARNING (src) = 1; } return NULL_TREE; @@ -605,9 +649,12 @@ c_strlen (tree src, int only_value) constructed with build_string will have nulls appended, we win even if we get handed something like (char[4])"abcd". - Since OFFSET is our starting index into the string, no further + Since ELTOFF is our starting index into the string, no further calculation is needed. */ - return ssize_int (strlen (ptr + offset)); + unsigned len = string_length (ptr + eltoff * eltsize, eltsize, + maxelts - eltoff); + + return ssize_int (len); } /* Return a constant integer corresponding to target reading diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index 92f939ebe21..3138ad3624f 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -79,6 +79,11 @@ along with GCC; see the file COPYING3. If not see #include "substring-locations.h" #include "diagnostic.h" +/* The likely worst case value of MB_LEN_MAX for the target, large enough + for UTF-8. Ideally, this would be obtained by a target hook if it were + to be used for optimization but it's good enough as is for warnings. */ +#define target_mb_len_max 6 + namespace { const pass_data pass_data_sprintf_length = { @@ -150,17 +155,30 @@ struct format_result unsigned HOST_WIDE_INT number_chars_max; /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX - is the output of all directives determined to be bounded to some - subrange of their types or possible lengths, false otherwise. + can be relied on for value range propagation, false otherwise. + This means that BOUNDED must not be set if the number of bytes + produced by any directive is unspecified or implementation- + defined (unless the implementation's behavior is known and + determined via a target hook). Note that BOUNDED only implies that the length of a function's output is known to be within some range, not that it's constant - and a candidate for folding. */ + and a candidate for string folding. BOUNDED is a stronger + guarantee than KNOWNRANGE. */ bool bounded; + /* True when the range above is obtained from known values of + directive arguments or their bounds and not the result of + heuristics that depend on warning levels. It is used to + issue stricter diagnostics in cases where strings of unknown + lengths are bounded by the arrays they are determined to + refer to. KNOWNRANGE must not be used to set the range of + the return value of a call. */ + bool knownrange; + /* True when the output of the formatted call is constant (and thus a candidate for string constant folding). This is rare and typically requires that the arguments of all directives - are also constant. Constant implies bounded. */ + are also constant. CONSTANT implies BOUNDED. */ bool constant; /* True if no individual directive resulted in more than 4095 bytes @@ -216,15 +234,31 @@ target_int_min () return int_min; } -/* Return the value of INT_MAX for the target. */ +/* Return the largest value for TYPE on the target. */ static unsigned HOST_WIDE_INT -target_int_max () +target_max_value (tree type) { - const unsigned HOST_WIDE_INT int_max + const unsigned HOST_WIDE_INT max_value = HOST_WIDE_INT_M1U >> (HOST_BITS_PER_WIDE_INT - - TYPE_PRECISION (integer_type_node) + 1); - return int_max; + - TYPE_PRECISION (type) + 1); + return max_value; +} + +/* Return the value of INT_MAX for the target. */ + +static inline unsigned HOST_WIDE_INT +target_int_max () +{ + return target_max_value (integer_type_node); +} + +/* Return the value of SIZE_MAX for the target. */ + +static inline unsigned HOST_WIDE_INT +target_size_max () +{ + return target_max_value (size_type_node); } /* Return the constant initial value of DECL if available or DECL @@ -412,6 +446,12 @@ struct result_range struct fmtresult { + fmtresult () + : argmin (), argmax (), knownrange (), bounded (), constant () + { + range.min = range.max = HOST_WIDE_INT_MAX; + } + /* The range a directive's argument is in. */ tree argmin, argmax; @@ -419,11 +459,17 @@ struct fmtresult results in on output for an argument in the range above. */ result_range range; + /* True when the range above is obtained from a known value of + a directive's argument or its bounds and not the result of + heuristics that depend on warning levels. */ + bool knownrange; + /* True when the range is the result of an argument determined to be bounded to a subrange of its type or value (such as by value range propagation or the width of the formt directive), false otherwise. */ bool bounded; + /* True when the output of a directive is constant. This is rare and typically requires that the argument(s) of the directive are also constant (such as determined by constant propagation, @@ -730,7 +776,7 @@ format_integer (const conversion_spec &, tree); static fmtresult format_pointer (const conversion_spec &spec, tree arg) { - fmtresult res = fmtresult (); + fmtresult res; /* Determine the target's integer format corresponding to "%p". */ const char *flags; @@ -869,14 +915,7 @@ format_integer (const conversion_spec &spec, tree arg) break; default: - { - fmtresult res = fmtresult (); - res.range.min = HOST_WIDE_INT_MAX; - res.range.max = HOST_WIDE_INT_MAX; - res.bounded = false; - res.constant = false; - return res; - } + return fmtresult (); } /* The type of the argument to the directive, either deduced from @@ -897,12 +936,13 @@ format_integer (const conversion_spec &spec, tree arg) { /* The minimum and maximum number of bytes produced by the directive. */ - fmtresult res = fmtresult (); + 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; @@ -975,12 +1015,10 @@ format_integer (const conversion_spec &spec, tree arg) /* Don't bother with invalid arguments since they likely would have already been diagnosed, and disable any further checking of the format string by returning [-1, -1]. */ - fmtresult res = fmtresult (); - res.range.min = res.range.max = HOST_WIDE_INT_M1U; - return res; + return fmtresult (); } - fmtresult res = fmtresult (); + fmtresult res; /* Using either the range the non-constant argument is in, or its type (either "formal" or actual), create a range of values that @@ -1029,9 +1067,10 @@ format_integer (const conversion_spec &spec, tree arg) argmax = res.argmax; } - /* The argument is bounded by the range of values determined - by Value Range Propagation. */ + /* The argument is bounded by the known range of values + determined by Value Range Propagation. */ res.bounded = true; + res.knownrange = true; } else if (range_type == VR_ANTI_RANGE) { @@ -1047,6 +1086,12 @@ format_integer (const conversion_spec &spec, tree arg) if (is_gimple_assign (def)) { tree_code code = gimple_assign_rhs_code (def); + if (code == INTEGER_CST) + { + arg = gimple_assign_rhs1 (def); + return format_integer (spec, arg); + } + if (code == NOP_EXPR) argtype = TREE_TYPE (gimple_assign_rhs1 (def)); } @@ -1111,7 +1156,7 @@ format_integer (const conversion_spec &spec, tree arg) SPEC the largest value in the real floating TYPE. */ static int -format_floating_max (tree type, char spec) +format_floating_max (tree type, char spec, int prec = -1) { machine_mode mode = TYPE_MODE (type); @@ -1136,9 +1181,21 @@ format_floating_max (tree type, char spec) mpfr_init2 (x, rfmt->p); mpfr_from_real (x, &rv, GMP_RNDN); - const char fmt[] = { '%', 'R', spec, '\0' }; - int n = mpfr_snprintf (NULL, 0, fmt, x); - return n; + 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 a range representing the minimum and maximum number of bytes @@ -1170,19 +1227,11 @@ format_floating (const conversion_spec &spec, int width, int prec) break; default: - { - fmtresult res = fmtresult (); - res.range.min = HOST_WIDE_INT_MAX; - res.range.max = HOST_WIDE_INT_MAX; - res.bounded = false; - res.constant = false; - return res; - } + return fmtresult (); } /* The minimum and maximum number of bytes produced by the directive. */ - fmtresult res = fmtresult (); - res.constant = false; + fmtresult res; /* Log10 of of the maximum number of exponent digits for the type. */ int logexpdigs = 2; @@ -1206,13 +1255,11 @@ 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); - /* Compute the maximum just once. */ - static const int a_max[] = { - format_floating_max (double_type_node, 'a'), - format_floating_max (long_double_type_node, 'a') - }; - res.range.max = a_max [ldbl]; + /* The output of "%a" is fully specified only when precision + is explicitly specified. */ + res.bounded = -1 < prec; break; } @@ -1229,6 +1276,9 @@ format_floating (const conversion_spec &spec, int width, int prec) 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; break; } @@ -1245,6 +1295,9 @@ format_floating (const conversion_spec &spec, int width, int prec) format_floating_max (long_double_type_node, 'f') }; res.range.max = f_max [ldbl]; + + /* "%f" is fully specified and the range of bytes is bounded. */ + res.bounded = true; break; } case 'G': @@ -1259,18 +1312,14 @@ format_floating (const conversion_spec &spec, int width, int prec) format_floating_max (long_double_type_node, 'g') }; res.range.max = g_max [ldbl]; + + /* "%g" is fully specified and the range of bytes is bounded. */ + res.bounded = true; break; } default: - { - fmtresult res = fmtresult (); - res.range.min = HOST_WIDE_INT_MAX; - res.range.max = HOST_WIDE_INT_MAX; - res.bounded = false; - res.constant = false; - return res; - } + return fmtresult (); } if (width > 0) @@ -1281,9 +1330,6 @@ format_floating (const conversion_spec &spec, int width, int prec) res.range.max = width; } - /* The argument is only considered bounded when the range of output - bytes is exact. */ - res.bounded = res.range.min == res.range.max; return res; } @@ -1298,7 +1344,7 @@ format_floating (const conversion_spec &spec, tree arg) int prec = -1; /* The minimum and maximum number of bytes produced by the directive. */ - fmtresult res = fmtresult (); + fmtresult res; res.constant = arg && TREE_CODE (arg) == REAL_CST; if (spec.have_width) @@ -1390,7 +1436,16 @@ format_floating (const conversion_spec &spec, tree arg) *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval); } - res.bounded = res.range.min < target_int_max (); + /* 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.range.min < target_int_max ()); + + /* The range of output is known even if the result isn't bounded. */ + res.knownrange = true; return res; } @@ -1405,14 +1460,7 @@ static fmtresult get_string_length (tree str) { if (!str) - { - fmtresult res; - res.range.min = HOST_WIDE_INT_MAX; - res.range.max = HOST_WIDE_INT_MAX; - res.bounded = false; - res.constant = false; - return res; - } + return fmtresult (); if (tree slen = c_strlen (str, 1)) { @@ -1421,6 +1469,7 @@ get_string_length (tree str) res.range.min = res.range.max = tree_to_shwi (slen); res.bounded = true; res.constant = true; + res.knownrange = true; return res; } @@ -1434,7 +1483,7 @@ get_string_length (tree str) if (lenrange [0] || lenrange [1]) { - fmtresult res = fmtresult (); + fmtresult res; res.range.min = (tree_fits_uhwi_p (lenrange[0]) ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length); @@ -1445,11 +1494,13 @@ get_string_length (tree str) by STR are known to be bounded (though not necessarily by their actual length but perhaps by their maximum possible length). */ res.bounded = res.range.max < target_int_max (); + res.knownrange = res.bounded; /* Set RES.CONSTANT to false even though that may be overly conservative in rare cases like: 'x ? a : b' where a and b have the same lengths and consist of the same characters. */ res.constant = false; + return res; } @@ -1479,7 +1530,7 @@ format_string (const conversion_spec &spec, tree arg) prec = (TREE_CODE (spec.star_precision) == INTEGER_CST ? tree_to_shwi (spec.star_precision) : -1); - fmtresult res = fmtresult (); + fmtresult res; /* The maximum number of bytes for an unknown wide character argument to a "%lc" directive adjusted for precision but not field width. */ @@ -1515,13 +1566,16 @@ format_string (const conversion_spec &spec, tree arg) locale, which is unfortunately, unknown. */ res.range.min = 1 == warn_format_length ? !nul : nul < 1; res.range.max = max_bytes_for_unknown_wc; - res.bounded = true; + /* The range above is good enough to issue warnings but not + for value range propagation, so clear BOUNDED. */ + res.bounded = false; } else { - /* A plain '%c' directive. */ + /* A plain '%c' directive. Its ouput is exactly 1. */ res.range.min = res.range.max = 1; res.bounded = true; + res.knownrange = true; res.constant = arg && TREE_CODE (arg) == INTEGER_CST; } } @@ -1533,24 +1587,35 @@ format_string (const conversion_spec &spec, tree arg) { gcc_checking_assert (slen.range.min == slen.range.max); - res.bounded = true; - /* A '%s' directive with a string argument with constant length. */ 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; + if (spec.modifier == FMT_LEN_l) { - if (warn_format_length > 2) + bounded = false; + + if (warn_format_length > 1) { - res.range.min *= 6; + /* Leave the minimum number of bytes the wide string + converts to equal to its length and set the maximum + to the worst case length which is the string length + multiplied by MB_LEN_MAX. */ /* It's possible to be smarter about computing the maximum by scanning the wide string for any 8-bit characters and if it contains none, using its length for the maximum. Even though this would be simple to do it's unlikely to be worth it when dealing with wide characters. */ - res.range.max *= 6; + res.range.max *= target_mb_len_max; } + /* For a wide character string, use precision as the maximum even if precision is greater than the string length since the number of bytes the string converts to may be greater @@ -1559,7 +1624,12 @@ format_string (const conversion_spec &spec, tree arg) res.range.max = prec; } else - res.constant = true; + { + /* The output od a "%s" directive with a constant argument + is bounded, constant, and obviously in a known range. */ + res.bounded = true; + res.constant = true; + } if (0 <= prec && (unsigned)prec < res.range.min) { @@ -1577,9 +1647,11 @@ format_string (const conversion_spec &spec, tree arg) if (0 <= prec) { - if ((unsigned)prec < slen.range.min - || slen.range.min >= target_int_max ()) + if (slen.range.min >= target_int_max ()) + slen.range.min = max_bytes_for_unknown_str; + else if ((unsigned)prec < slen.range.min) slen.range.min = prec; + if ((unsigned)prec < slen.range.max || slen.range.max >= target_int_max ()) slen.range.max = prec; @@ -1597,6 +1669,7 @@ format_string (const conversion_spec &spec, tree arg) specified to limit the number of bytes or when the number of bytes is known or contrained to some range. */ res.bounded = 0 <= prec || slen.bounded; + res.knownrange = slen.knownrange; res.constant = false; } } @@ -1613,6 +1686,11 @@ format_string (const conversion_spec &spec, tree arg) && bounded) res.bounded = true; + /* When precision is specified the range of characters on output + is known to be bounded by it. */ + if (-1 < prec) + res.knownrange = true; + return res; } @@ -1655,29 +1733,46 @@ format_directive (const pass_sprintf_length::call_info &info, /* Compute the (approximate) length of the formatted output. */ fmtresult fmtres = spec.fmtfunc (spec, arg); - /* The overall result is bounded only if the output of every - directive is exact or bounded. */ - res->bounded = res->bounded && fmtres.bounded; - res->constant = res->constant && fmtres.constant; + /* The overall result is bounded and constant only if the output + of every directive is bounded and constant, respectively. */ + res->bounded &= fmtres.bounded; + res->constant &= fmtres.constant; - if (fmtres.range.max >= HOST_WIDE_INT_MAX) - { - /* Disable exact and maximum length checking after a failure - to determine the maximum number of characters (for example - for wide characters or wide character strings) but continue - tracking the minimum number of characters. */ - res->number_chars_max = HOST_WIDE_INT_M1U; - res->number_chars = HOST_WIDE_INT_M1U; - } + /* Record whether the output of all directives is known to be + bounded by some maximum, implying that their arguments are + either known exactly or determined to be in a known range + or, for strings, limited by the upper bounds of the arrays + they refer to. */ + res->knownrange &= fmtres.knownrange; - if (fmtres.range.min >= HOST_WIDE_INT_MAX) + if (!fmtres.knownrange) { - /* Disable exact length checking after a failure to determine - even the minimum number of characters (it shouldn't happen - except in an error) but keep tracking the minimum and maximum - number of characters. */ - res->number_chars = HOST_WIDE_INT_M1U; - return; + /* 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 ()) + { + /* Normalize the MAX counter to avoid having to deal with it + later. The counter can be less than HOST_WIDE_INT_M1U + when compiling for an ILP32 target on an LP64 host. */ + fmtres.range.max = HOST_WIDE_INT_M1U; + /* Disable exact and maximum length checking after a failure + to determine the maximum number of characters (for example + for wide characters or wide character strings) but continue + tracking the minimum number of characters. */ + res->number_chars_max = HOST_WIDE_INT_M1U; + res->number_chars = HOST_WIDE_INT_M1U; + } + + if (fmtres.range.min >= target_int_max ()) + { + /* Disable exact length checking after a failure to determine + even the minimum number of characters (it shouldn't happen + except in an error) but keep tracking the minimum and maximum + number of characters. */ + res->number_chars = HOST_WIDE_INT_M1U; + return; + } } /* Compute the number of available bytes in the destination. There @@ -1725,11 +1820,15 @@ format_directive (const pass_sprintf_length::call_info &info, } } else if (navail < fmtres.range.max - && (fmtres.bounded || 1 < warn_format_length)) + && (((spec.specifier == 's' + && fmtres.range.max < HOST_WIDE_INT_MAX) + /* && (spec.precision || spec.star_precision) */) + || 1 < warn_format_length)) { /* The maximum directive output is longer than there is - room in the destination and the output is either bounded - or the warning level is greater than 1. */ + room in the destination and the output length is either + explicitly constrained by the precision (for strings) + or the warning level is greater than 1. */ if (fmtres.range.max >= HOST_WIDE_INT_MAX) { const char* fmtstr @@ -1910,10 +2009,13 @@ add_bytes (const pass_sprintf_length::call_info &info, /* If issuing a diagnostic (only when one hasn't already been issued), distinguish between a possible overflow ("may write") and a certain - overflow somewhere "past the end." (Ditto for truncation.) */ + overflow somewhere "past the end." (Ditto for truncation.) + KNOWNRANGE is used to warn even at level 1 about possibly writing + past the end or truncation due to strings of unknown lengths that + are bounded by the arrays they are known to refer to. */ if (!res->warned && (avail_range.max < nbytes - || ((res->bounded || 1 < warn_format_length) + || ((res->knownrange || 1 < warn_format_length) && avail_range.min < nbytes))) { /* Set NAVAIL to the number of available bytes used to decide @@ -1921,7 +2023,7 @@ add_bytes (const pass_sprintf_length::call_info &info, warning will depend on AVAIL_RANGE. */ unsigned HOST_WIDE_INT navail = avail_range.max; if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX - && (res->bounded || 1 < warn_format_length)) + && (res->knownrange || 1 < warn_format_length)) navail = avail_range.min; /* Compute the offset of the first format character that is beyond @@ -1932,8 +2034,12 @@ add_bytes (const pass_sprintf_length::call_info &info, size_t len = strlen (info.fmtstr + off); + /* Create a location that underscores the substring of the format + string that is or may be written past the end (or is or may be + truncated), pointing the caret at the first character of the + substring. */ substring_loc loc - (info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0, + (info.fmtloc, TREE_TYPE (info.format), off, len ? off : 0, off + len - !!len); /* Is the output of the last directive the result of the argument @@ -1944,7 +2050,7 @@ add_bytes (const pass_sprintf_length::call_info &info, = (res->number_chars_min < res->number_chars_max && res->number_chars_min < info.objsize); - if (!end && (nbytes - navail) == 1) + if (!end && ((nbytes - navail) == 1 || boundrange)) { /* There is room for the rest of the format string but none for the terminating nul. */ @@ -2082,10 +2188,11 @@ pass_sprintf_length::compute_format_length (const call_info &info, /* Reset exact, minimum, and maximum character counters. */ res->number_chars = res->number_chars_min = res->number_chars_max = 0; - /* No directive has been seen yet so the output is bounded and constant - (with no conversion producing more than 4K bytes) until determined - otherwise. */ + /* No directive has been seen yet so the length of output is bounded + by the known range [0, 0] and constant (with no conversion producing + more than 4K bytes) until determined otherwise. */ res->bounded = true; + res->knownrange = true; res->constant = true; res->under4k = true; res->floating = false; @@ -2591,10 +2698,10 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi) if (TREE_CODE (size) == INTEGER_CST) { dstsize = tree_to_uhwi (size); - /* No object can be larger than HOST_WIDE_INT_MAX bytes - (half the address space). This imposes a limit that's - one byte less than that. */ - if (dstsize >= HOST_WIDE_INT_MAX) + /* 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. */ + if (dstsize >= target_size_max () / 2) warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, "specified destination size %wu too large", dstsize); @@ -2640,7 +2747,7 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi) info.objsize = dstsize < objsize ? dstsize : objsize; if (info.bounded - && dstsize != HOST_WIDE_INT_M1U && objsize < dstsize) + && dstsize < target_size_max () / 2 && objsize < dstsize) { warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, "specified size %wu exceeds the size %wu " diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index b520b295dff..28c755036d8 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,14 @@ +2016-10-24 Martin Sebor + + PR middle-end/77735 + * gcc.dg/tree-ssa/builtin-sprintf-2.c: Add test cases. + * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same. + * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same. + * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Adjust/relax. + * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Add test cases. + * gcc.dg/tree-ssa/builtin-sprintf-warn-6.c: XFAIL for LP64 only. + * gcc.dg/tree-ssa/builtin-sprintf.c: Add test cases. + 2016-10-24 Richard Biener PR testsuite/71491 diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c index d6a0e6bd81d..1996665e2b3 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c @@ -1,5 +1,5 @@ /* Test to verify that the return value of calls to __builtin_sprintf - is not folded if the call has undefined behavior even if it would + is not folded if the call isn't fully specified, even if it would otherwise produce a known number of bytes on output, and that if the return value is in a known range the range is not made available to subsequent passes and doesn't affect branching and @@ -22,7 +22,8 @@ char buf8k [8192]; #define CAT(a, b) concat (a, b) #define EQL(expect, size, fmt, ...) \ - void CAT (test_on_line_, __LINE__)(void) \ + void __attribute__ ((noinline, noclone)) \ + CAT (test_on_line_, __LINE__)(void) \ { \ if (!LINE || LINE == __LINE__) \ { \ @@ -37,7 +38,8 @@ char buf8k [8192]; to the formatted function is not treated as a constant or made available to subsequent optimization passes. */ #define RNG(min, max, size, fmt, ...) \ - void CAT (test_on_line_, __LINE__)(void) \ + void __attribute__ ((noinline, noclone)) \ + CAT (test_on_line_, __LINE__)(void) \ { \ if (!LINE || LINE == __LINE__) \ { \ @@ -52,6 +54,9 @@ extern int i; extern long li; extern char *str; +extern double d; +extern long double ld; + /* Verify that overflowing the destination object disables the return value optimization. */ EQL (0, 0, "%c", ' '); @@ -78,7 +83,15 @@ enum { imax2 = (INT_MAX / 2) * 2 }; EQL (imax2, -1, "%*c%*c", INT_MAX / 2, 'x', INT_MAX / 2, 'y'); /* Verify that range information for calls that overflow the destination - isn't available. */ + isn't available. + + +-- lower bound of the tested range + | +-- upper bound of the tested range + | | +-- size of destination buffer + | | | +-- format string + | | | | +-- argument(s) + | | | | | + V V V V V */ RNG (0, 0, 0, "%hhi", i) RNG (0, 0, 1, "%hhi", i) RNG (0, 1, 1, "%hhi", i) @@ -190,11 +203,36 @@ RNG (0, 10, 10, "%i", i) #endif +/* Verify that the output of a "%a" directive with no precision is not + considered constant or within a known range (the number of digits + after the decimal point is unspecified in this case). The hardcoded + ranges correspond to Glibc values. */ +RNG (6, 6, 7, "%a", 0.0) /* Glibc output: "0x0p+0" */ +RNG (6, 6, 7, "%a", d) +RNG (6, 6, 7, "%.4096a", d) + +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 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 + returning a negative value as Glibc can for some directives). */ +RNG (0, INT_MAX, -1, "%-s", str); + /* Verify the result of a conditional expression involving a string literal and an unknown string isn't optimized. */ RNG (0, 1, 4, "%-s", i ? str : "123"); RNG (0, 1, 4, "%-s", i ? "123" : str); +/* Verfy that the output involving wide strings is not optimized + (the output is actually bounded by a function of MB_LEN_MAX + which should be at least 6 to accommodate UTF-8 but this isn't + implemented yet). */ +RNG (0, 5, 7, "%ls", L"1"); +RNG (0, 6, 8, "%s%ls", "1", L"2"); + /* Verify that no call to abort has been eliminated and that each call is at the beginning of a basic block (and thus the result of a branch). This latter test tries to verify that the test preceding the call to @@ -214,5 +252,5 @@ RNG (0, 1, 4, "%-s", i ? "123" : str); */ -/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 105 "optimized" { target { ilp32 || lp64 } } } } */ -/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 74 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } } */ +/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 114 "optimized" { target { ilp32 || lp64 } } } } */ +/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 83 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c index 4616e0f82e4..5779a95f8df 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c @@ -37,6 +37,18 @@ typedef __WINT_TYPE__ wint_t; typedef unsigned char UChar; +/* Constants used to verify the pass can determine their values even + without optimization. */ +const int cst0 = 0; +const int cst1 = 1; +const int cst10 = 10; + +/* Initialized global variables used to verify that the pass doesn't + use their initial values (they could be modified by calls to other + functions). */ +int var0 = 0; +int var10 = 10; + const char s0[] = ""; const char s1[] = "1"; const char s2[] = "12"; @@ -372,6 +384,13 @@ void test_sprintf_chk_s_const (void) T (3, "%.0ls", L"1"); T (3, "%.1ls", L"1"); T (3, "%.2ls", L"1"); + T (3, "%ls", L"12"); + + T (3, "%ls", L"123"); /* { dg-warning "nul past the end" } */ + T (3, "%.0ls", L"123"); + T (3, "%.1ls", L"123"); + T (3, "%.2ls", L"123"); + T (3, "%.3ls", L"123"); /* { dg-warning "nul past the end" } */ } /* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives @@ -382,7 +401,9 @@ void test_sprintf_chk_hh_const (void) T (-1, "%hhd", 0); T (1, "%hhd", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hhd", cst0); /* { dg-warning "nul past the end" } */ T (1, "%hhd", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hhd", cst1); /* { dg-warning "nul past the end" } */ T (1, "%hhd", -1); /* { dg-warning "into a region" } */ T (1, "%+hhd", 0); /* { dg-warning "into a region" } */ T (1, "%+hhd", 1); /* { dg-warning "into a region" } */ @@ -402,6 +423,7 @@ void test_sprintf_chk_hh_const (void) T (2, "%+hhi", 9); /* { dg-warning "nul past the end" } */ T (2, "%-hhi", 9); T (2, "%hhi", 10); /* { dg-warning "nul past the end" } */ + T (2, "%hhi", cst10); /* { dg-warning "nul past the end" } */ T (2, "%hhi", -1); /* { dg-warning "nul past the end" } */ T (2, "% hhi", -1); /* { dg-warning "nul past the end" } */ T (2, "%+hhi", -1); /* { dg-warning "nul past the end" } */ @@ -863,6 +885,35 @@ void test_sprintf_chk_z_const (void) T ( 2, "%zu", (size_t)10); /* { dg-warning "nul past the end" } */ } +void test_sprintf_chk_a_const (void) +{ + T (-1, "%a", 0.0); + T (-1, "%la", 0.0); + + /* The least number of bytes on output is 6 for "0x0p+0". When precision + is missing the number of digits after the decimal point isn't fully + specified by C (it seems like a defect). */ + T (0, "%a", 0.0); /* { dg-warning "into a region" } */ + T (0, "%la", 0.0); /* { dg-warning "into a region" } */ + T (1, "%a", 0.0); /* { dg-warning "into a region" } */ + T (2, "%a", 0.0); /* { dg-warning "into a region" } */ + T (3, "%a", 0.0); /* { dg-warning "into a region" } */ + T (4, "%a", 0.0); /* { dg-warning "into a region" } */ + T (5, "%a", 0.0); /* { dg-warning "into a region" } */ + T (6, "%a", 0.0); /* { dg-warning "writing a terminating nul" } */ + T (7, "%a", 0.0); + + T (0, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (0, "%.0la", 0.0); /* { dg-warning "into a region" } */ + T (1, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (2, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (3, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (4, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (5, "%.0a", 0.0); /* { dg-warning "into a region" } */ + T (6, "%.0a", 0.0); /* { dg-warning "writing a terminating nul" } */ + T (7, "%.0a", 0.0); +} + void test_sprintf_chk_e_const (void) { T (-1, "%E", 0.0); @@ -893,20 +944,23 @@ void test_sprintf_chk_e_const (void) T ( 6, "%.0e", 1.0); /* The actual output of the following directives depends on the rounding - mode. Verify that the warning correctly reflects that. */ - T (12, "%e", 9.999999e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%e", 9.9999994e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%e", 9.9999995e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%e", 9.9999996e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%e", 9.9999997e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%e", 9.9999998e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */ - - T (12, "%Le", 9.9999994e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%Le", 9.9999995e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%Le", 9.9999996e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%Le", 9.9999997e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%Le", 9.9999998e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ - T (12, "%Le", 9.9999999e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */ + mode. Verify that the warning correctly reflects that. At level 1, + since the minimum number of bytes output by the directive fits the + space the directive itself isn't diagnosed but the terminating nul + is. The directive is diagnosed at level 2. */ + T (12, "%e", 9.999999e+99); /* { dg-warning "terminating nul" } */ + T (12, "%e", 9.9999994e+99); /* { dg-warning "terminating nul" } */ + T (12, "%e", 9.9999995e+99); /* { dg-warning "terminating nul" } */ + T (12, "%e", 9.9999996e+99); /* { dg-warning "terminating nul" } */ + T (12, "%e", 9.9999997e+99); /* { dg-warning "terminating nul" } */ + T (12, "%e", 9.9999998e+99); /* { dg-warning "terminating nul" } */ + + T (12, "%Le", 9.9999994e+99L);/* { dg-warning "terminating nul" } */ + T (12, "%Le", 9.9999995e+99L);/* { dg-warning "terminating nul" } */ + T (12, "%Le", 9.9999996e+99L);/* { dg-warning "terminating nul" } */ + T (12, "%Le", 9.9999997e+99L);/* { dg-warning "terminating nul" } */ + T (12, "%Le", 9.9999998e+99L);/* { dg-warning "terminating nul" } */ + T (12, "%Le", 9.9999999e+99L);/* { dg-warning "terminating nul" } */ } /* At -Wformat-length level 1 unknown numbers are assumed to have @@ -936,12 +990,14 @@ void test_sprintf_chk_hh_nonconst (int a) { T (-1, "%hhd", a); - T (0, "%hhd", a); /* { dg-warning "into a region" } */ + T (0, "%hhd", a); /* { dg-warning ".%hhd. directive writing between 1 and . bytes into a region of size 0" } */ + T (0, "%hhi", var0); /* { dg-warning "into a region" } */ T (0, "%hhi", a); /* { dg-warning "into a region" } */ T (0, "%hhu", a); /* { dg-warning "into a region" } */ T (0, "%hhx", a); /* { dg-warning "into a region" } */ T (1, "%hhd", a); /* { dg-warning "nul past the end" } */ + T (1, "%hhd", var0); /* { dg-warning "nul past the end" } */ T (1, "%hhi", a); /* { dg-warning "nul past the end" } */ T (1, "%hhu", a); /* { dg-warning "nul past the end" } */ T (1, "%hhx", a); /* { dg-warning "nul past the end" } */ @@ -954,19 +1010,23 @@ void test_sprintf_chk_hh_nonconst (int a) T (1, "%-hhi", a); /* { dg-warning "nul past the end" } */ T (2, "%hhd", a); + T (2, "%hhd", var0); + T (2, "%hhd", var10); T (2, "%hhi", a); T (2, "%hho", a); T (2, "%hhu", a); T (2, "%hhx", a); T (2, "% hhd", a); /* { dg-warning "nul past the end" } */ + T (2, "% hhd", var0); /* { dg-warning "nul past the end" } */ + T (2, "% hhd", var10); /* { dg-warning "nul past the end" } */ T (2, "% hhi", a); /* { dg-warning "nul past the end" } */ T (2, "% hho", a); /* { dg-warning ". . flag used with .%o." } */ T (2, "% hhu", a); /* { dg-warning ". . flag used with .%u." } */ T (2, "% hhx", a); /* { dg-warning ". . flag used with .%x." } */ - T (2, "#%hho", a); /* { dg-warning "nul past the end" } */ - T (2, "#%hhx", a); /* { dg-warning "nul past the end" } */ + T (2, "%#hho", a); /* { dg-warning "nul past the end" } */ + T (2, "%#hhx", a); /* { dg-warning ".%#hhx. directive writing between 3 and . bytes into a region of size 2" } */ T (3, "%2hhd", a); T (3, "%2hhi", a); @@ -1086,8 +1146,7 @@ void test_sprintf_chk_e_nonconst (double d) T ( 1, "%e", d); /* { dg-warning "into a region" } */ T ( 2, "%e", d); /* { dg-warning "into a region" } */ T ( 3, "%e", d); /* { dg-warning "into a region" } */ - T (12, "%e", d); /* { dg-warning "past the end" } */ - T (12, "%e", d); /* { dg-warning "past the end" } */ + T (12, "%e", d); /* { dg-warning "nul past the end" } */ T (13, "%E", d); /* 1.000000E+00 */ T (13, "%e", d); T (14, "%E", d); diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c index 675ecac4f72..e19768e1b65 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c @@ -47,10 +47,13 @@ void test_s_const (void) T (1, "%*ls", 0, L"\0"); T (1, "%*ls", 1, L""); /* { dg-warning "nul past the end" } */ - T (1, "%ls", L"1"); /* { dg-warning "nul past the end" } */ + T (1, "%ls", L"1"); /* { dg-warning "directive writing between 1 and 6 bytes into a region of size 1" } */ T (1, "%.0ls", L"1"); T (2, "%.0ls", L"1"); T (2, "%.1ls", L"1"); + T (2, "%.2ls", L"1"); /* { dg-warning "nul past the end" } */ + T (2, "%.3ls", L"1"); /* { dg-warning "directive writing between 1 and 3 bytes into a region of size 2" } */ + T (2, "%.2ls", L"12"); /* { dg-warning "nul past the end" } */ /* The "%.2ls" directive below will write at a minimum 1 byte (because L"1" is known and can be assumed to convert to at least one multibyte @@ -64,6 +67,12 @@ void test_s_const (void) T (2, "%.0ls", L"1"); T (2, "%.1ls", L"1"); T (3, "%.2ls", L"1"); + T (3, "%.2ls", L"12"); + T (3, "%.3ls", L"12"); /* { dg-warning "nul past the end" } */ + T (4, "%.3ls", L"123"); + T (4, "%.4ls", L"123"); /* { dg-warning "nul past the end" } */ + T (4, "%.5ls", L"123"); /* { dg-warning "directive writing between 3 and 5 bytes into a region of size 4" } */ + T (4, "%.6ls", L"123"); /* { dg-warning "directive writing between 3 and 6 bytes into a region of size 4" } */ } @@ -86,7 +95,9 @@ void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a) T (1, "%.0s", s); T (1, "%.1s", s); /* { dg-warning "writing a terminating nul" } */ - T (1, "%ls", ws); /* { dg-warning "writing a terminating nul" } */ + T (1, "%.0ls", ws); + T (1, "%.1ls", ws); /* { dg-warning "writing a terminating nul" } */ + T (1, "%ls", ws); /* { dg-warning "writing a terminating nul" } */ /* Verify that the size of the array is used in lieu of its length. The minus sign disables GCC's sprintf to strcpy transformation. */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c index 625d055bafb..8d97fa879ab 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c @@ -175,9 +175,9 @@ void test_sprintf_chk_range_schar (signed char *a) T ( 3, "%i", R ( 0, 100)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ /* The following call may write as few as 3 bytes and as many as 5. - It's judgment call how best to diagnose it to make the potential + It's a judgment call how best to diagnose it to make the potential problem clear. */ - T ( 3, "%i%i", R (1, 10), R (9, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */ + T ( 3, "%i%i", R (1, 10), R (9, 10)); /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */ T ( 4, "%i%i", R (10, 11), R (12, 13)); /* { dg-warning "nul past the end" } */ @@ -187,7 +187,7 @@ void test_sprintf_chk_range_schar (signed char *a) T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10)); /* { dg-warning "may write a terminating nul past the end" } */ T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */ T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9)); /* { dg-warning "may write a terminating nul past the end" } */ - T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */ + T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */ } void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b) diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c index 4b40336127e..faa5806aa52 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c @@ -3,31 +3,91 @@ extern int sprintf (char*, const char*, ...); -char dst [8]; +char dst [3]; void test (void) { - sprintf (dst + 7, "%-s", "1"); - /* { dg-warning "writing a terminating nul past the end of the destination" "" { target *-*-* } 10 } - { dg-message "format output 2 bytes into a destination of size 1" "" { target *-*-* } 10 } - { dg-begin-multiline-output "" } - sprintf (dst + 7, "%-s", "1"); - ~~^~ - { dg-end-multiline-output "" } - { dg-begin-multiline-output "" } - sprintf (dst + 7, "%-s", "1"); + /* Verify thet the caret points to the (invisible) nul character + at the end of the format string (i.e., its closing quote). + The redundant argument is there to get around GCC bug 77799. */ + sprintf (dst + 2, "1", 0); + /* { dg-warning "writing a terminating nul past the end of the destination" "nul warning" { target *-*-* } .-1 } + { dg-message "format output 2 bytes into a destination of size 1" "note" { target *-*-* } .-2 } + { dg-begin-multiline-output "-Wformat output: redundant argument" } + sprintf (dst + 2, "1", 0); + ^~~ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "-Wformat-length output" } + sprintf (dst + 2, "1", 0); + ~^ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "note" } + sprintf (dst + 2, "1", 0); + ^~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + + /* Verify thet the caret points at the first format character written + past the end of the destination. */ + sprintf (dst, "1234", 0); + /* { dg-warning "writing format character .4. at offset 3 past the end of the destination" "nul warning" { target *-*-* } .-1 } + { dg-message "format output 5 bytes into a destination of size 3" "note" { target *-*-* } .-2 } + { dg-begin-multiline-output "-Wformat output: redundant argument" } + sprintf (dst, "1234", 0); + ^~~~~~ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "-Wformat-length output" } + sprintf (dst, "1234", 0); + ^ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "note" } + sprintf (dst, "1234", 0); + ^~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + + /* Verify thet the caret points at the first format character written + past the end of the destination and the rest of the format string + is underlined. */ + sprintf (dst, "12345", 0); + /* { dg-warning "writing format character .4. at offset 3 past the end of the destination" "nul warning" { target *-*-* } .-1 } + { dg-message "format output 6 bytes into a destination of size 3" "note" { target *-*-* } .-2 } + { dg-begin-multiline-output "-Wformat output: redundant argument" } + sprintf (dst, "12345", 0); + ^~~~~~~ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "-Wformat-length output" } + sprintf (dst, "12345", 0); + ^~ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "note" } + sprintf (dst, "12345", 0); + ^~~~~~~~~~~~~~~~~~~~~~~~~ + { dg-end-multiline-output "" } */ + + /* Same as above but with a directive. The minus flag is used to + get around GCC bug 77671. */ + sprintf (dst + 2, "%-s", "1"); + /* { dg-warning "writing a terminating nul past the end of the destination" "warning" { target *-*-* } .-1 } + { dg-message "format output 2 bytes into a destination of size 1" "note" { target *-*-* } .-2 } + { dg-begin-multiline-output "-Wformat-length output" } + sprintf (dst + 2, "%-s", "1"); + ~~~^ + { dg-end-multiline-output "" } + { dg-begin-multiline-output "note" } + sprintf (dst + 2, "%-s", "1"); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { dg-end-multiline-output "" } */ - sprintf (dst + 7, "%-s", "abcd"); - /* { dg-warning ".%-s. directive writing 4 bytes into a region of size 1" "" { target *-*-* } 22 } - { dg-message "format output 5 bytes into a destination of size 1" "" { target *-*-* } 22 } - { dg-begin-multiline-output "" } - sprintf (dst + 7, "%-s", "abcd"); + sprintf (dst + 2, "%-s", "abcd"); + /* { dg-warning ".%-s. directive writing 4 bytes into a region of size 1" "warning" { target *-*-* } .-1 } + { dg-message "format output 5 bytes into a destination of size 1" "note" { target *-*-* } .-2 } + { dg-begin-multiline-output "-Wformat-length output" } + sprintf (dst + 2, "%-s", "abcd"); ^~~ ~~~~~~ { dg-end-multiline-output "" } - { dg-begin-multiline-output "" } - sprintf (dst + 7, "%-s", "abcd"); + { dg-begin-multiline-output "note" } + sprintf (dst + 2, "%-s", "abcd"); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ { dg-end-multiline-output "" } */ } + +/* { dg-prune-output "too many arguments for format" } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c index 1a9df982d71..0cb02b78e81 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-6.c @@ -88,5 +88,5 @@ void fllong (long j, char *p) if (k > 999) return; - snprintf (p, 4, "%3llu", k); /* { dg-bogus "" "unsigned long long" { xfail *-*-* } } */ + snprintf (p, 4, "%3llu", k); /* { dg-bogus "" "unsigned long long" { xfail lp64 } } */ } diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c index b8b332fe8c8..916df79bb9b 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c @@ -359,23 +359,27 @@ test_x (unsigned char uc, unsigned short us, unsigned ui) } static void __attribute__ ((noinline, noclone)) -test_a_double (void) +test_a_double (double d) { - EQL ( 6, 7, "%a", 0.0); /* 0x0p+0 */ - EQL ( 6, 7, "%a", 1.0); /* 0x8p-3 */ - EQL ( 6, 7, "%a", 2.0); /* 0x8p-2 */ - + EQL ( 6, 7, "%.0a", 0.0); /* 0x0p+0 */ + EQL ( 6, 7, "%.0a", 1.0); /* 0x8p-3 */ + EQL ( 6, 7, "%.0a", 2.0); /* 0x8p-2 */ EQL ( 8, 9, "%.1a", 3.0); /* 0xc.0p-2 */ - EQL ( 9, 10, "%.2a", 4.0); /* 0xa.00p-1 */ + EQL ( 9, 10, "%.2a", 4.0); /* 0x8.00p-1 */ + EQL (10, 11, "%.3a", 5.0); /* 0xa.000p-1 */ + + /* d is in [ 0, -DBL_MAX ] */ + RNG ( 6, 10, 11, "%.0a", d); /* 0x0p+0 ... -0x2p+1023 */ + RNG ( 6, 12, 13, "%.1a", d); /* 0x0p+0 ... -0x2.0p+1023 */ + RNG ( 6, 13, 14, "%.2a", d); /* 0x0p+0 ... -0x2.00p+1023 */ } static void __attribute__ ((noinline, noclone)) test_a_long_double (void) { - EQL ( 6, 7, "%La", 0.0L); /* 0x0p+0 */ - EQL ( 6, 7, "%La", 1.0L); /* 0x8p-3 */ - EQL ( 6, 7, "%La", 2.0L); /* 0x8p-2 */ - + EQL ( 6, 7, "%.0La", 0.0L); /* 0x0p+0 */ + EQL ( 6, 7, "%.0La", 1.0L); /* 0x8p-3 */ + EQL ( 6, 7, "%.0La", 2.0L); /* 0x8p-2 */ EQL ( 8, 9, "%.1La", 3.0L); /* 0xc.0p-2 */ EQL ( 9, 10, "%.2La", 4.0L); /* 0xa.00p-1 */ } @@ -525,7 +529,7 @@ int main (void) test_d_i (0xdeadbeef, 0xdeadbeefL); test_x ('?', 0xdead, 0xdeadbeef); - test_a_double (); + test_a_double (0.0); test_e_double (); test_f_double (); -- 2.30.2