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/78521 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/78521 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. diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c index dc2b66d..9f07503 100644 --- a/gcc/gimple-ssa-sprintf.c +++ b/gcc/gimple-ssa-sprintf.c @@ -235,23 +235,12 @@ target_int_min () 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 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 tree_to_uhwi (TYPE_MAX_VALUE (integer_type_node)); } /* Return the value of SIZE_MAX for the target. */ @@ -259,7 +248,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 @@ -843,6 +832,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, 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 @@ -860,18 +886,10 @@ format_integer (const conversion_spec &spec, tree arg) if (!intmax_type_node) build_intmax_type_nodes (&intmax_type_node, &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'; @@ -940,15 +958,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; @@ -981,25 +992,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; } @@ -1110,8 +1152,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); @@ -1261,11 +1305,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; } @@ -1278,13 +1324,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; } @@ -1300,10 +1349,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': @@ -1317,10 +1367,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; } @@ -1346,6 +1397,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; @@ -1358,12 +1412,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) @@ -1374,6 +1429,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; } @@ -1413,9 +1469,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 @@ -1442,16 +1498,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; } @@ -1521,20 +1585,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; @@ -1594,11 +1648,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) { @@ -1626,19 +1681,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 { @@ -1652,10 +1720,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; } @@ -1678,20 +1746,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; @@ -1807,7 +1878,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 @@ -1821,6 +1892,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' @@ -2277,13 +2361,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 @@ -2328,6 +2421,7 @@ pass_sprintf_length::compute_format_length (const call_info &info, } } + start_precision: if ('.' == *pf) { ++pf; @@ -2345,7 +2439,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) @@ -2705,9 +2804,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)) @@ -2719,10 +2818,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 %", dstsize); } else if (TREE_CODE (size) == SSA_NAME) diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c index d568f9c..cdaeeac 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c @@ -44,6 +44,26 @@ void test_arg_int (int i, int n) for (i = -n; i != n; ++i) T (8, "%08x", i); + + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (0, "%.0d", ival (0)); + T (0, "%.0i", ival (0)); + T (0, "%.0o", ival (0)); + T (0, "%.0u", ival (0)); + T (0, "%.0x", ival (0)); + + T (0, "%.*d", 0, ival (0)); + T (0, "%.*i", 0, ival (0)); + T (0, "%.*o", 0, ival (0)); + T (0, "%.*u", 0, ival (0)); + T (0, "%.*x", 0, ival (0)); + + T (1, "%1.0d", ival (0)); + T (1, "%1.0i", ival (0)); + T (1, "%1.0o", ival (0)); + T (1, "%1.0u", ival (0)); + T (1, "%1.0x", ival (0)); } void test_arg_string (const char *s) diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c new file mode 100644 index 0000000..375fc09 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-6.c @@ -0,0 +1,73 @@ +/* PR middle-end/78476 - snprintf(0, 0, ...) with known arguments not + optimized away + A negative test complementing builtin-sprintf-5.c to verify that calls + to the function that do not return a constant are not optimized away. + { dg-compile } + { dg-options "-O2 -fdump-tree-optimized" } + { dg-require-effective-target int32plus } */ + +#define CONCAT(a, b) a ## b +#define CAT(a, b) CONCAT (a, b) + +#define T(...) \ + do { \ + int CAT (n, __LINE__) = __builtin_snprintf (0, 0, __VA_ARGS__); \ + sink (CAT (n, __LINE__)); \ + } while (0) + +void sink (int); + +static int +int_range (int min, int max) +{ + extern int int_value (void); + int val = int_value (); + if (val < min || max < val) + val = min; + return val; +} + +#define R(min, max) int_range (min, max) + +void test_arg_int (int width, int prec, int i, int n) +{ + T ("%i", i); + T ("%1i", i); + T ("%2i", i); + T ("%3i", i); + T ("%4i", i); + + T ("%*i", width, 0); + T ("%*i", width, 1); + T ("%*i", width, i); + + T ("%.*i", prec, 0); + T ("%.*i", prec, 1); + T ("%.*i", prec, i); + T ("%.*i", 0, i); + + T ("%i", R (1, 10)); + + for (i = -n; i != n; ++i) + T ("%*x", n, i); +} + +void test_arg_string (int width, int prec, const char *s) +{ + T ("%-s", s); + T ("%1s", s); + T ("%.1s", s); + T ("%*s", width, s); + T ("%.*s", prec, s); + T ("%1.*s", prec, s); + T ("%*.1s", width, s); + T ("%*.*s", width, prec, s); + T ("%*s", width, "123"); + T ("%.*s", prec, "123"); + T ("%1.*s", prec, "123"); + T ("%*.1s", width, "123"); + T ("%*.*s", width, prec, "123"); +} + + +/* { dg-final { scan-tree-dump-times "snprintf" 27 "optimized"} } */ 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 a24889b..4aafc9f 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 @@ -233,6 +233,8 @@ void test_sprintf_chk_s_const (void) T ( 1, "%*s", 1, s0); /* { dg-warning "nul past the end" } */ T (-1, "%*s", 1, s0); /* No warning for unknown destination size. */ + T (1, "%.s", ""); + T (1, "%.s", "123"); T (1, "%.0s", "123"); T (1, "%.0s", s3); T (1, "%.*s", 0, "123"); @@ -450,6 +452,24 @@ void test_sprintf_chk_hh_const (void) T (4, "%hhi %hhi", 10, 1); /* { dg-warning "nul past the end" } */ T (4, "%hhi %hhi", 11, 12); /* { dg-warning "into a region" } */ + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0hhd", 0); + T (1, "%+.0hhd", 0); + T (1, "%-.0hhd", 0); + T (1, "% .0hhd", 0); + T (1, "%0.0hhd", 0); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0hhd", 0); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0hhd", 0); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0hhi", 0); + T (1, "%.0hho", 0); + T (1, "%#.0hho", 0); + T (1, "%.0hhx", 0); + T (1, "%.0hhX", 0); + T (1, "%#.0hhX", 0); + T (5, "%0*hhd %0*hhi", 0, 7, 0, 9); T (5, "%0*hhd %0*hhi", 1, 7, 1, 9); T (5, "%0*hhd %0*hhi", 1, 7, 2, 9); @@ -546,14 +566,32 @@ void test_sprintf_chk_h_const (void) T (4, "%#hx", 0x100); /* { dg-warning "into a region" } */ T (4, "%#hx", -1); /* { dg-warning "into a region" } */ + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0hd", 0); + T (1, "%+.0hd", 0); + T (1, "%-.0hd", 0); + T (1, "% .0hd", 0); + T (1, "%0.0hd", 0); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0hd", 0); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0hd", 0); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0hi", 0); + T (1, "%.0ho", 0); + T (1, "%#.0ho", 0); + T (1, "%.0hx", 0); + T (1, "%.0hX", 0); + T (1, "%#.0hX", 0); + #undef MAX #define MAX 65535 - T (1, "%hhu", 0); /* { dg-warning "nul past the end" } */ - T (1, "%hhu", 1); /* { dg-warning "nul past the end" } */ - T (1, "%hhu", -1); /* { dg-warning "into a region" } */ - T (1, "%hhu", MAX); /* { dg-warning "into a region" } */ - T (1, "%hhu", MAX + 1); /* { dg-warning "nul past the end" } */ + T (1, "%hu", 0); /* { dg-warning "nul past the end" } */ + T (1, "%hu", 1); /* { dg-warning "nul past the end" } */ + T (1, "%hu", -1); /* { dg-warning "into a region" } */ + T (1, "%hu", MAX); /* { dg-warning "into a region" } */ + T (1, "%hu", MAX + 1); /* { dg-warning "nul past the end" } */ } /* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with @@ -611,6 +649,24 @@ void test_sprintf_chk_integer_const (void) T ( 8, "%8u", 1); /* { dg-warning "nul past the end" } */ T ( 9, "%8u", 1); + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0d", 0); + T (1, "%+.0d", 0); + T (1, "%-.0d", 0); + T (1, "% .0d", 0); + T (1, "%0.0d", 0); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0d", 0); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0d", 0); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0i", 0); + T (1, "%.0o", 0); + T (1, "%#.0o", 0); + T (1, "%.0x", 0); + T (1, "%.0X", 0); + T (1, "%#.0X", 0); + T ( 7, "%1$i%2$i%3$i", 1, 23, 456); T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456); T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456); /* { dg-warning "nul past the end" } */ @@ -691,6 +747,24 @@ void test_sprintf_chk_j_const (void) T ( 8, "%8ju", I (1)); /* { dg-warning "nul past the end" } */ T ( 9, "%8ju", I (1)); + + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0jd", I (0)); + T (1, "%+.0jd", I (0)); + T (1, "%-.0jd", I (0)); + T (1, "% .0jd", I (0)); + T (1, "%0.0jd", I (0)); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0jd", I (0)); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0jd", I (0)); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0ji", I (0)); + T (1, "%.0jo", I (0)); + T (1, "%#.0jo", I (0)); + T (1, "%.0jx", I (0)); + T (1, "%.0jX", I (0)); + T (1, "%#.0jX", I (0)); } /* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives @@ -747,6 +821,24 @@ void test_sprintf_chk_l_const (void) T ( 8, "%8lu", 1L); /* { dg-warning "nul past the end" } */ T ( 9, "%8lu", 1L); + + /* As a special case, a precision of zero with an argument of zero + results in zero bytes (unless modified by width). */ + T (1, "%.0ld", 0L); + T (1, "%+.0ld", 0L); + T (1, "%-.0ld", 0L); + T (1, "% .0ld", 0L); + T (1, "%0.0ld", 0L); /* { dg-warning ".0. flag ignored with precision" } */ + T (1, "%00.0ld", 0L); /* { dg-warning "repeated .0. flag in format" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%-0.0ld", 0L); /* { dg-warning ".0. flag ignored with .-. flag" } */ + /* { dg-warning ".0. flag ignored with precision" "" { target *-*-* } .-1 } */ + T (1, "%.0li", 0L); + T (1, "%.0lo", 0L); + T (1, "%#.0lo", 0L); + T (1, "%.0lx", 0L); + T (1, "%.0lX", 0L); + T (1, "%#.0lX", 0L); } /* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives @@ -858,37 +950,56 @@ void test_sprintf_chk_z_const (void) void test_sprintf_chk_a_const (void) { - T (-1, "%a", 0.0); - T (-1, "%la", 0.0); + T (-1, "%a", 0.0); + T (-1, "%la", 0.0); + T (-1, "%.a", 0.0); + T (-1, "%.la", 0.0); + T (-1, "%123.a", 0.0); + T (-1, "%234.la", 0.0); + T (-1, "%.345a", 0.0); + T (-1, "%456.567la", 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 (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 (0, "%.a", 0.0); /* { dg-warning "into a region" } */ + 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, "%6.a", 0.0); + T (7, "%7.a", 0.0); /* { dg-warning "writing a terminating nul" } */ + T (7, "%7.1a", 0.0); /* { dg-warning "writing 8 bytes into a region of size 7" } */ + + T (7, "%.a", 0.0); T (7, "%.0a", 0.0); } void test_sprintf_chk_e_const (void) { - T (-1, "%E", 0.0); - T (-1, "%lE", 0.0); + T (-1, "%E", 0.0); + T (-1, "%lE", 0.0); + T (-1, "%.E", 0.0); + T (-1, "%.lE", 0.0); + T (-1, "%123.E", 0.0); + T (-1, "%234.lE", 0.0); + T (-1, "%.345E", 0.0); + T (-1, "%.456lE", 0.0); T ( 0, "%E", 0.0); /* { dg-warning "into a region" } */ T ( 0, "%e", 0.0); /* { dg-warning "into a region" } */ @@ -910,8 +1021,10 @@ void test_sprintf_chk_e_const (void) T (16, "%.8e", -1.9e+104); /* { dg-warning "nul past the end" } */ T (17, "%.8e", -2.0e+105); /* -2.00000000e+105 */ + T ( 5, "%.e", 0.0); /* { dg-warning "nul past the end" } */ T ( 5, "%.0e", 0.0); /* { dg-warning "nul past the end" } */ T ( 5, "%.0e", 1.0); /* { dg-warning "nul past the end" } */ + T ( 6, "%.e", 1.0); T ( 6, "%.0e", 1.0); /* The actual output of the following directives depends on the rounding @@ -938,7 +1051,7 @@ void test_sprintf_chk_e_const (void) the value one, and unknown strings are assumed to have a zero length. */ -void test_sprintf_chk_s_nonconst (int i, const char *s) +void test_sprintf_chk_s_nonconst (int w, int p, const char *s) { T (-1, "%s", s); T ( 0, "%s", s); /* { dg-warning "nul past the end" } */ @@ -946,6 +1059,19 @@ void test_sprintf_chk_s_nonconst (int i, const char *s) T ( 1, "%.0s", s); T ( 1, "%.1s", s); /* { dg-warning "nul past the end" } */ + /* The string argument is constant but the width and/or precision + is not. */ + T ( 1, "%*s", w, ""); + T ( 1, "%*s", w, "1"); /* { dg-warning "nul past the end" } */ + T ( 1, "%.*s", w, ""); + T ( 1, "%.*s", w, "1"); /* { dg-warning "may write a terminating nul" } */ + T ( 1, "%.*s", w, "123"); /* { dg-warning "writing between 0 and 3 bytes into a region of size 1" } */ + + T ( 1, "%*s", w, "123"); /* { dg-warning "writing 3 or more bytes into a region of size 1" } */ + T ( 2, "%*s", w, "123"); /* { dg-warning "writing 3 or more bytes into a region of size 2" } */ + T ( 3, "%*s", w, "123"); /* { dg-warning "writing a terminating nul past the end" } */ + T ( 4, "%*s", w, "123"); + /* The following will definitely write past the end of the buffer, but since at level 1 the length of an unknown string argument is assumed to be zero, it will write the terminating nul past @@ -957,7 +1083,7 @@ void test_sprintf_chk_s_nonconst (int i, const char *s) /* Exercise the hh length modifier with all integer specifiers and a non-constant argument. */ -void test_sprintf_chk_hh_nonconst (int a) +void test_sprintf_chk_hh_nonconst (int w, int p, int a) { T (-1, "%hhd", a); @@ -999,11 +1125,48 @@ void test_sprintf_chk_hh_nonconst (int a) 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, "%0hhd", a); + T (3, "%1hhd", a); T (3, "%2hhd", a); T (3, "%2hhi", a); T (3, "%2hho", a); T (3, "%2hhu", a); T (3, "%2hhx", a); + T (3, "%2.hhx", a); + + T (3, "%3hhd", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hhi", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hho", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hhu", a); /* { dg-warning "nul past the end" } */ + T (3, "%3hhx", a); /* { dg-warning "nul past the end" } */ + T (3, "%3.hhx", a); /* { dg-warning "nul past the end" } */ + + T (4, "%5hhd", a); /* { dg-warning "into a region" } */ + T (4, "%6hhi", a); /* { dg-warning "into a region" } */ + T (4, "%7hho", a); /* { dg-warning "into a region" } */ + T (4, "%8hhu", a); /* { dg-warning "into a region" } */ + T (4, "%9hhx", a); /* { dg-warning "into a region" } */ + + T (3, "%.hhd", a); + T (3, "%.0hhd", a); + T (3, "%.1hhd", a); + T (3, "%.2hhd", a); + T (3, "%.2hhi", a); + T (3, "%.2hho", a); + T (3, "%.2hhu", a); + T (3, "%.2hhx", a); + + T (3, "%.3hhd", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hhi", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hho", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hhu", a); /* { dg-warning "nul past the end" } */ + T (3, "%.3hhx", a); /* { dg-warning "nul past the end" } */ + + T (4, "%.5hhd", a); /* { dg-warning "into a region" } */ + T (4, "%.6hhi", a); /* { dg-warning "into a region" } */ + T (4, "%.7hho", a); /* { dg-warning "into a region" } */ + T (4, "%.8hhu", a); /* { dg-warning "into a region" } */ + T (4, "%.9hhx", a); /* { dg-warning "into a region" } */ /* Exercise cases where the type of the actual argument (whose value and range are unknown) constrain the size of the output and so @@ -1012,6 +1175,55 @@ void test_sprintf_chk_hh_nonconst (int a) T (2, "%hhd", (UChar)a); T (2, "%hhi", (UChar)a); T (2, "%-hhi", (UChar)a); + + /* Exercise cases where the argument is known but width isn't. */ + T (0, "%*hhi", w, 0); /* { dg-warning "into a region" } */ + T (1, "%*hhi", w, 0); /* { dg-warning "nul past the end" } */ + T (2, "%*hhi", w, 0); + T (2, "%*hhi", w, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*hhi", w, 123); /* { dg-warning "into a region" } */ + + /* The argument is known but precision isn't. When the argument + is zero only the first call can be diagnosed since a zero + precision would result in no bytes on output. */ + T (0, "%.*hhi", p, 0); /* { dg-warning "nul past the end" } */ + T (1, "%.*hhi", p, 0); + T (2, "%.*hhi", p, 0); + T (2, "%.*hhi", p, 12); /* { dg-warning "nul past the end" } */ + T (2, "%.*hhi", p, 123); /* { dg-warning "into a region" } */ + + /* The argument is known but neither width nor precision is. */ + T (0, "%*.*hhi", w, p, 0); /* { dg-warning "nul past the end" } */ + T (1, "%*.*hhi", w, p, 0); + T (2, "%*.*hhi", w, p, 0); + T (2, "%*.*hhi", w, p, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*.*hhi", w, p, 123); /* { dg-warning "into a region" } */ + + /* The argument and width are known but precision isn't. */ + T (0, "%1.*hhi", p, 0); /* { dg-warning "into a region" } */ + T (0, "%-1.*hhi", p, 0); /* { dg-warning "into a region" } */ + T (1, "%1.*hhi", p, 0); /* { dg-warning "nul past the end" } */ + T (2, "%1.*hhi", p, 0); + T (2, "%2.*hhi", p, 0); /* { dg-warning "nul past the end" } */ + T (2, "%1.*hhi", p, 12); /* { dg-warning "nul past the end" } */ + T (2, "%2.*hhi", p, 12); /* { dg-warning "nul past the end" } */ + + T (2, "%1.*hhi", p, 123); /* { dg-warning "into a region" } */ + T (2, "%2.*hhi", p, 123); /* { dg-warning "into a region" } */ + T (2, "%3.*hhi", p, 123); /* { dg-warning "into a region" } */ + + /* The argument and precision are known but width isn't. */ + T (0, "%*.1hhi", w, 0); /* { dg-warning "into a region" } */ + T (1, "%*.1hhi", w, 0); /* { dg-warning "nul past the end" } */ + T (2, "%*.1hhi", w, 0); + T (2, "%*.2hhi", w, 0); /* { dg-warning "nul past the end" } */ + T (2, "%*.1hhi", w, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*.2hhi", w, 12); /* { dg-warning "nul past the end" } */ + T (2, "%*.3hhi", w, 12); /* { dg-warning "into a region" } */ + + T (2, "%*.1hhi", w, 123); /* { dg-warning "into a region" } */ + T (2, "%*.2hhi", w, 123); /* { dg-warning "into a region" } */ + T (2, "%*.3hhi", w, 123); /* { dg-warning "into a region" } */ } /* Exercise the h length modifier with all integer specifiers and @@ -1063,7 +1275,7 @@ void test_sprintf_chk_h_nonconst (int a) /* Exercise all integer specifiers with no modifier and a non-constant argument. */ -void test_sprintf_chk_int_nonconst (int a) +void test_sprintf_chk_int_nonconst (int w, int p, int a) { T (-1, "%d", a); @@ -1104,12 +1316,22 @@ void test_sprintf_chk_int_nonconst (int a) T (3, "%2o", a); T (3, "%2u", a); T (3, "%2x", a); + + T (1, "%.*d", p, a); } -void test_sprintf_chk_e_nonconst (double d) +void test_sprintf_chk_e_nonconst (int w, int p, double d) { - T (-1, "%E", d); - T (-1, "%lE", d); + T (-1, "%E", d); + T (-1, "%lE", d); + T (-1, "%.E", d); + T (-1, "%.lE", d); + T (-1, "%*E", w, d); + T (-1, "%*lE", w, d); + T (-1, "%.*E", p, d); + T (-1, "%.*lE", p, d); + T (-1, "%*.*E", w, p, d); + T (-1, "%*.*lE", w, p, d); T ( 0, "%E", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ T ( 0, "%e", d); /* { dg-warning "into a region" } */ @@ -1123,9 +1345,9 @@ void test_sprintf_chk_e_nonconst (double d) T (14, "%E", d); T (14, "%e", d); - T (0, "%+E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ - T (0, "%-e", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ - T (0, "% E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ + T ( 0, "%+E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ + T ( 0, "%-e", d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ + T ( 0, "% E", d); /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */ /* The range of output of "%.0e" is between 5 and 7 bytes (not counting the terminating NUL. */ @@ -1136,6 +1358,9 @@ void test_sprintf_chk_e_nonconst (double d) the terminating NUL. */ T ( 7, "%.1e", d); /* { dg-warning "writing a terminating nul past the end" } */ T ( 8, "%.1e", d); + + T ( 0, "%*e", 0, d); /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */ + T ( 0, "%*e", w, d); /* { dg-warning "writing 12 or more bytes into a region of size 0" } */ } void test_sprintf_chk_f_nonconst (double d) @@ -1204,7 +1429,6 @@ void test_vsprintf_chk_c (__builtin_va_list va) /* Here in the best case each argument will format as single character, causing the terminating NUL to be written past the end. */ T (3, "%lc%c%c"); - } void test_vsprintf_chk_int (__builtin_va_list va) @@ -1254,9 +1478,11 @@ void test_vsprintf_chk_int (__builtin_va_list va) #define T(size, fmt, ...) \ __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__) -void test_snprintf_c_const (void) +void test_snprintf_c_const (char *d) { - T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ too large" } */ + T (-1, "%c", 0); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin_snprintf (d, INT_MAX, "%c", 0); /* Verify the full text of the diagnostic for just the distinct messages and use abbreviations in subsequent test cases. */ @@ -1306,7 +1532,7 @@ void test_snprintf_chk_c_const (void) the function by __builtin_object_size) is diagnosed. */ __builtin___snprintf_chk (buffer, 3, 0, 2, " "); /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */ - T (-1, "%c", 0); /* { dg-warning "specified destination size \[^ \]* too large" } */ + T (-1, "%c", 0); /* { dg-warning "specified destination size \[^ \]* is too large" } */ T (0, "%c", 0); T (0, "%c%c", 0, 0); @@ -1417,7 +1643,7 @@ void test_vsprintf_int (__builtin_va_list va) void test_vsnprintf_s (__builtin_va_list va) { - T (-1, "%s"); /* { dg-warning "specified destination size \[^ \]* too large" } */ + T (-1, "%s"); /* { dg-warning "specified destination size \[^ \]* is too large" } */ T (0, "%s"); T (1, "%s"); @@ -1442,7 +1668,7 @@ void test_vsnprintf_chk_s (__builtin_va_list va) the function by __builtin_object_size) is diagnosed. */ __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va); /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */ - __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va); /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */ + __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va); /* { dg-warning "always overflow|destination size .\[0-9\]+. is too large" } */ T (0, "%s"); T (1, "%s"); 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 8d97fa8..f4550ba 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 @@ -1,6 +1,8 @@ /* { dg-do compile } */ /* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ +typedef __SIZE_TYPE__ size_t; + #ifndef LINE # define LINE 0 #endif @@ -232,3 +234,48 @@ void test_sprintf_chk_range_sshort (signed short *a, signed short *b) T ( 4, "%i", Ra (998, 999)); T ( 4, "%i", Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ } + +/* Verify that destination size in excess of INT_MAX (and, separately, + in excess of the largest object) is diagnosed. The former because + the functions are defined only for output of at most INT_MAX and + specifying a large upper bound defeats the bounds checking (and, + on some implementations such as Solaris, causes the function to + fail. The latter because due to the limit of ptrdiff_t no object + can be larger than PTRDIFF_MAX bytes. */ + +void test_too_large (char *d, int x, __builtin_va_list va) +{ + const size_t imax = __INT_MAX__; + const size_t imax_p1 = imax + 1; + + __builtin_snprintf (d, imax, "%c", x); + __builtin_snprintf (d, imax_p1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + __builtin_vsnprintf (d, imax, "%c", va); + __builtin_vsnprintf (d, imax_p1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + __builtin___snprintf_chk (d, imax, 0, imax, "%c", x); + __builtin___snprintf_chk (d, imax_p1, 0, imax_p1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + __builtin___vsnprintf_chk (d, imax, 0, imax, "%c", va); + __builtin___vsnprintf_chk (d, imax_p1, 0, imax_p1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." { target lp64 } } */ + /* { dg-warning "specified destination size \[0-9\]+ is too large" "" { target { ilp32 } } .-1 } */ + + const size_t ptrmax = __PTRDIFF_MAX__; + const size_t ptrmax_m1 = ptrmax - 1; + + __builtin_snprintf (d, ptrmax_m1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin_snprintf (d, ptrmax, " %c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin_vsnprintf (d, ptrmax_m1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin_vsnprintf (d, ptrmax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin___snprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin___snprintf_chk (d, ptrmax, 0, ptrmax, "%c", x); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ + + __builtin___vsnprintf_chk (d, ptrmax_m1, 0, ptrmax_m1, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ exceeds .INT_MAX." "" { target lp64 } } */ + __builtin___vsnprintf_chk (d, ptrmax, 0, ptrmax, "%c", va); /* { dg-warning "specified destination size \[0-9\]+ is too large" } */ +}